English | 简体中文 | 繁體中文 | Русский язык | Français | Español | Português | Deutsch | 日本語 | 한국어 | Italiano | بالعربية

Android RefreshLayout 드래그 다운 레이아웃 구현

项目中需要下拉刷新的功能,但是这个 View 不是 ListView 这类的控件,需要 ViewGroup 实现这个功能,一开始网上大略找了一下,没发现特别合适的,代码也是没怎么看懂,所以决定还是自己写一个。

  于是翻出 XlistView 的源码一点一点看,再大致理解了 XLisview 源码,终于决定自己动手啦

  为了省事,headView 还是用了 XListView 的 HeadView,省了很多事:)

  下拉刷新,下拉刷新,肯定是先实现下拉功能,最开始我是打算通过 extends ScrollView 来实现,因为有现成的滚动效果嘛,可是实际因为两个原因放弃了:

1、在ScrollView下只能有一个子控件View,虽然在Scroll下添加一个ViewGroup,然后讲headView动态添加进前面的ViewGroup,但是我还是比较习惯studio的可视化预览,总觉得不直观!

2、当ScrollView内嵌ListView时会发生冲突,还需要去重写ListView。于是放弃换个思路!

 关于上面的原因1:动态添加headView进ScrollView的中GroupView中,可以在重写ScrollView的onViewAdded()方法,将初始化时解析的headView添加进子GroupView

@Override 
public void onViewAdded(View child) { 
  super.onViewAdded(child); 
  //因为headView要在最上面,最先想到的就是Vertical的LinearLayout 
  LinearLayout linearLayout = (LinearLayout) getChildAt(0); 
  linearLayout.addView(view, 0); 
} 

  换个思路,通过extends LinearLayout来实现吧!
先做准备工作,我们需要一个HeaderView以及要获取到HeaderView的高度,还有初始时Layout的高度

private void initView(Context context) { 
   mHeaderView = new SRefreshHeader(context); 
   mHeaderViewContent = (RelativeLayout) mHeaderView.findViewById(R.id.slistview_header_content); 
   setOrientation(VERTICAL); 
   addView(mHeaderView, 0); 
   getHeaderViewHeight(); 
   getViewHeight(); 
 } 

mHeaderView = new SRefreshHeader(context);
通过构造方法实例化HeaderView

mHeaderViewContent = (RelativeLayout)

mHeaderView.findViewById(R.id.slistview_header_content);

 这是解析headerView内容区域iew,等会儿要获取这个view的高度,你肯定会问为啥不用上面的mHeaderView来获取高度,点进构造方法里可以看到如下代码

// 초기 상태에서,下拉刷新view의 높이를 0으로 설정합니다 
LayoutParams lp = new LayoutParams(LayoutParams.MATCH_PARENT, 0); 
mContainer = (LinearLayout) LayoutInflater.from(context).inflate(R.layout.listview_head_view_layout, null); 
w(mContainer, lp); 

如果直接获取mHeaderView的高度,那肯定是0
getHeaderViewHeight();
getViewHeight();
分别是获取HeaderView的高度和Layout的初始高度

/** 
  * 获取headView高度 
  */ 
  private void getHeaderViewHeight() { 
    ViewTreeObserver vto2 = mHeaderViewContent.getViewTreeObserver(); 
    vto2.addOnGlobalLayoutListener(new ViewTreeObserver.OnGlobalLayoutListener() { 
      @Override 
      public void onGlobalLayout() { 
        mHeaderViewHeight = mHeaderViewContent.getHeight(); 
        mHeaderViewContent.getViewTreeObserver().removeGlobalOnLayoutListener(this); 
      } 
    }); 
  } 
  /** 
  * 获取SRefreshLayout当前实例的高度 
  */ 
  private void getViewHeight() { 
    ViewTreeObserver thisView = getViewTreeObserver(); 
    thisView.addOnGlobalLayoutListener(new ViewTreeObserver.OnGlobalLayoutListener() { 
      @Override 
      public void onGlobalLayout() { 
        SRefreshLayout.this.mHeight = SRefreshLayout.this.getHeight(); 
        SRefreshLayout.this.getViewTreeObserver().removeGlobalOnLayoutListener(this); 
      } 
    }); 
  } 

准备工作完成了,接下来就是要进行下拉操作了
到这里,肯定一下就想到了onTouchEvent()方法,是的!现在就开始在这里施工

下拉操作总共会经历三个过程
ACTION_UP→ACTION_MOVE→ACTION_UP
ACTION_UP 이벤트, 즉 손가락이 눌러진 순간, 우리가 할 일은 누른 순간의 좌표를 기록하는 것뿐입니다

switch (ev.getAction()) { 
      case MotionEvent.ACTION_DOWN: 
        //시작 높이를 기록합니다 
        mLastY = ev.getRawY();//터치를 누른 Y 좌표를 기록합니다 
        break; 

그런 다음 ACTION_MOVE 이벤트입니다. 여기가 가장 중요합니다.下拉할 때 HeadView와 Layout의 높이 변화는 여기서 이루어집니다

case MotionEvent.ACTION_MOVE: 
       if (!isRefreashing) 
         isRefreashing = true; 
       final float deltaY = ev.getRawY(); - mLastY; 
       mLastY = ev.getRawY(); 
       updateHeaderViewHeight(deltaY / 1.8f);//이동 거리를 일정한 비율로 축소합니다 
       updateHeight(); 
       break; 

안에 updateHeaderViewHeight와 updateHeight는 HeaderView의 높이와 Layout의 높이를 변경하는 것입니다

 private void updateHeight() { 
    ViewGroup.LayoutParams lp = getLayoutParams(); 
    //현재 레이아웃 인스턴스 높이를 헤더뷰 높이와 초기 레이아웃 높이를 더한 값을 설정합니다 
    //만약 레이아웃을 업데이트하지 않으면 내용 높이가 압축되어 비율을 유지할 수 없습니다 
    lp.height = (mHeight + mHeaderView.getVisiableHeight()); 
    setLayoutParams(lp); 
  } 
  private void updateHeaderViewHeight(float space) { 
//    if (space < 0) 
//      space = 0; 
//    int factHeight = (int) (space - mHeaderViewHeight); 
    if (mHeaderView.getStatus() != SRefreshHeader.STATE_REFRESHING) 
      //만약 새로고침 중이 아니고 높이 
      if (mHeaderView.getVisiableHeight() < mHeaderViewHeight) * 2 && mHeaderView.getStatus() != SRefreshHeader.STATE_NORMAL) 
        mHeaderView.setState(SRefreshHeader.STATE_NORMAL); 
      } 
      if (mHeaderView.getVisiableHeight() > mHeaderViewHeight * 2 && mHeaderView.getStatus() != SRefreshHeader.STATE_READY) { 
        mHeaderView.setState(SRefreshHeader.STATE_READY); 
      } 
    } 
    mHeaderView.setVisiableHeight((int) space 
        + mHeaderView.getVisiableHeight()); 
  } 

Header 높이를 업데이트할 때,下拉의 거리를 통해 새로 고침 거리에 도달했는지 확인합니다. 위 코드에서는 mHeaderView 초기 높이의 두 배에 도달하면 "빠르게 새로 고침" 상태로 들어갑니다. 도달하지 않으면 "下拉刷新" 상태를 유지합니다
HeaderView의 상태는 총3각각은

public final static int STATE_NORMAL = 0;//下拉 갱신 
 public final static int STATE_READY = 1;//갱신 해제 
 public final static int STATE_REFRESHING = 2;//갱신 중 

높이를 업데이트하는 방법은 headerView와 layout이 모두 동일합니다. 원래 높이에 이동한 거리를 더한 값을 headerView 또는 layout에 다시 할당합니다

mHeaderView.setVisiableHeight((int) space 
               + mHeaderView.getVisiableHeight()); 

마지막으로 ACTION_UP 이벤트입니다. 손가락이 화면에서 벗어나는 순간입니다. 여기서는 headerView의 현재 상태에 따라 headerView의 최종 상태를 결정해야 합니다!

case MotionEvent.ACTION_UP: 
        //터치를 떠났을 때 
        //클릭 이벤트가 발생하지 않도록 방지합니다 
        if (!isRefreashing) 
          break; 
        //headView의 상태가 READY 상태라면, 터치를 떼면 REFRESHING 상태로 들어가야 합니다 
        if (mHeaderView.getStatus() == SRefreshHeader.STATE_READY) { 
          mHeaderView.setState(SRefreshHeader.STATE_REFRESHING); 
        } 
        //현재 상태에 따라 SrefreshLayout의 현재 인스턴스와 headView의 높이를 초기화합니다 
        resetHeadView(mHeaderView.getStatus()); 
        reset(mHeaderView.getStatus()); 
        mLastY = -1;//좌표 초기화 
        break; 

resetHeadView와 reset은 headerView 높이와 layout 높이를 초기화하는 메서드입니다

private void reset(int status) { 
    ViewGroup.LayoutParams lp = getLayoutParams(); 
    switch (status) { 
      case SRefreshHeader.STATE_REFRESHING: 
        lp.height = mHeight + mHeaderViewHeight; 
        break; 
      case SRefreshHeader.STATE_NORMAL: 
        lp.height = mHeight; 
        break; 
    } 
    setLayoutParams(lp); 
  } 
  private void resetHeadView(int status) { 
    switch (status) { 
      case SRefreshHeader.STATE_REFRESHING: 
        mHeaderView.setVisiableHeight(mHeaderViewHeight); 
        break; 
      case SRefreshHeader.STATE_NORMAL: 
        mHeaderView.setVisiableHeight(0); 
        break; 
    } 
  } 

구현 방식도 동일합니다. 상태에 따라 판단합니다. refresh 중이면 headerView가 정상적으로 표시되고, 높이가 초기 높이입니다. NORMAL, 즉 "下拉刷新" 상태에서는, 새로 고침이 발생하지 않았습니다. 초기화할 때, headerView는 숨겨지거나 높이가 0으로 초기화됩니다

여기까지下拉刷新 작업도 기본적으로 완료되었습니다.回调 인터페이스를 추가하여 알림을 추가해야 합니다

interface OnRefreshListener { 
    void onRefresh(); 
  } 
case MotionEvent.ACTION_UP: 
        //터치를 떠났을 때 
        //클릭 이벤트가 발생하지 않도록 방지합니다 
        if (!isRefreashing) 
          break; 
        //headView의 상태가 READY 상태라면, 터치를 떼면 REFRESHING 상태로 들어가야 합니다 
        if (mHeaderView.getStatus() == SRefreshHeader.STATE_READY) { 
          mHeaderView.setState(SRefreshHeader.STATE_REFRESHING); 
          if (mOnRefreshListener != null) 
            mOnRefreshListener.onRefresh(); 
        } 
        //현재 상태에 따라 SrefreshLayout의 현재 인스턴스와 headView의 높이를 초기화합니다 
        resetHeadView(mHeaderView.getStatus()); 
        reset(mHeaderView.getStatus()); 
        mLastY = -1;//좌표 초기화 
        break; 

좋아, 여기까지는 기본적으로 완료되었습니다. 효과를 테스트해 보세요. 와우, 문제를 발견했습니다. ListView에 접근할 때 이 Layout이 왜下拉刷新를 수행할 수 없는지 생각해 보세요. 이벤트 분포 문제라는 것을 깨달았습니다. 이벤트 인터셉트를 처리해야 합니다!
이벤트 인터셉트 처리에 대해, 홍량 대신이 쓴 ViewGroup 이벤트 분포 블로그와 Android-Ultra-Pull-To-Refresh 부분의 원본 코드에서 해결책을 찾았습니다:

@Override 
  public boolean onInterceptTouchEvent(MotionEvent ev) { 
    AbsListView absListView = null; 
    for (int n = 0; n < getChildCount(); n++) { 
      if (getChildAt(n) instanceof AbsListView) { 
        absListView = (ListView) getChildAt(n); 
        Logs.v("查找到listView"); 
      } 
    } 
    if (absListView == null) 
      return super.onInterceptTouchEvent(ev); 
    switch (ev.getAction()) { 
      case MotionEvent.ACTION_DOWN: 
        mStartY = ev.getRawY(); 
        break; 
      case MotionEvent.ACTION_MOVE: 
        float space = ev.getRawY(); - mStartY; 
        Logs.v("space:" + space); 
        if (space > 0 && !absListView.canScrollVertically(-1) && absListView.getFirstVisiblePosition() == 0) { 
          Logs.v("차단 성공"); 
          return true; 
        } else { 
          Logs.v("불가지않음"); 
          return false; 
        } 
    } 
    return super.onInterceptTouchEvent(ev); 
  } 

그 중

if (space > 0 && !absListView.canScrollVertically(-1) && absListView.getFirstVisiblePosition() == 0)
space는 이동한 거리를 의미하며 canScrollVertically()는 ListView가 수직 방향으로 스크롤할 수 있는지 확인하는 함수입니다. 매개변수가 음수일 때는 위쪽으로, 양수일 때는 아래쪽으로 스크롤합니다. 마지막 것은 ListView의 첫 번째 보이는 item의 position입니다

위의 이벤트 차단 처리를 추가하여, 초기에 언급한 요구를 충족하는 ViewGroup가 완성되었습니다!

위에 Layout의 원본 코드와 HeaderView(직접 사용하는 XlistView의 HeaderView)의 원본 코드를 붙여넣습니다.

public class SRefreshLayout extends LinearLayout { 
  private SRefreshHeader mHeaderView; 
  private RelativeLayout mHeaderViewContent; 
  private boolean isRefreashing; 
  private float mLastY = -1;//누른 시작 높이 
  private int mHeaderViewHeight;//HeaderView 내용 높이 
  private int mHeight;//구성 요소 높이 
  private float mStartY; 
  interface OnRefreshListener { 
    void onRefresh(); 
  } 
  public OnRefreshListener mOnRefreshListener; 
  public SRefreshLayout(Context context) { 
    super(context); 
    initView(context); 
  } 
  public SRefreshLayout(Context context, AttributeSet attrs) { 
    super(context, attrs); 
    initView(context); 
  } 
  public SRefreshLayout(Context context, AttributeSet attrs, int defStyleAttr) { 
    super(context, attrs, defStyleAttr); 
    initView(context); 
  } 
  private void initView(Context context) { 
    mHeaderView = new SRefreshHeader(context); 
    mHeaderViewContent = (RelativeLayout) mHeaderView.findViewById(R.id.slistview_header_content); 
    setOrientation(VERTICAL); 
    addView(mHeaderView, 0); 
    getHeaderViewHeight(); 
    getViewHeight(); 
  } 
  /** 
   * 获取headView高度 
   */ 
  private void getHeaderViewHeight() { 
    ViewTreeObserver vto2 = mHeaderViewContent.getViewTreeObserver(); 
    vto2.addOnGlobalLayoutListener(new ViewTreeObserver.OnGlobalLayoutListener() { 
      @Override 
      public void onGlobalLayout() { 
        mHeaderViewHeight = mHeaderViewContent.getHeight(); 
        mHeaderViewContent.getViewTreeObserver().removeGlobalOnLayoutListener(this); 
      } 
    }); 
  } 
  /** 
   * 获取SRefreshLayout当前实例的高度 
   */ 
  private void getViewHeight() { 
    ViewTreeObserver thisView = getViewTreeObserver(); 
    thisView.addOnGlobalLayoutListener(new ViewTreeObserver.OnGlobalLayoutListener() { 
      @Override 
      public void onGlobalLayout() { 
        SRefreshLayout.this.mHeight = SRefreshLayout.this.getHeight(); 
        SRefreshLayout.this.getViewTreeObserver().removeGlobalOnLayoutListener(this); 
      } 
    }); 
  } 
  @Override 
  public boolean onInterceptTouchEvent(MotionEvent ev) { 
    AbsListView absListView = null; 
    for (int n = 0; n < getChildCount(); n++) { 
      if (getChildAt(n) instanceof AbsListView) { 
        absListView = (ListView) getChildAt(n); 
        Logs.v("查找到listView"); 
      } 
    } 
    if (absListView == null) 
      return super.onInterceptTouchEvent(ev); 
    switch (ev.getAction()) { 
      case MotionEvent.ACTION_DOWN: 
        mStartY = ev.getRawY(); 
        break; 
      case MotionEvent.ACTION_MOVE: 
        float space = ev.getRawY(); - mStartY; 
        Logs.v("space:" + space); 
        if (space > 0 && !absListView.canScrollVertically(-1) && absListView.getFirstVisiblePosition() == 0) { 
          Logs.v("차단 성공"); 
          return true; 
        } else { 
          Logs.v("불가지않음"); 
          return false; 
        } 
    } 
    return super.onInterceptTouchEvent(ev); 
  } 
  @Override 
  public boolean onTouchEvent(MotionEvent ev) { 
    if (mLastY == -1) 
      mLastY = ev.getRawY(); 
    switch (ev.getAction()) { 
      case MotionEvent.ACTION_DOWN: 
        //시작 높이를 기록합니다 
        mLastY = ev.getRawY();//터치를 누른 Y 좌표를 기록합니다 
        break; 
      //손가락이 스크린을 떠났을 때 
      case MotionEvent.ACTION_UP: 
        //터치를 떠났을 때 
        //클릭 이벤트가 발생하지 않도록 방지합니다 
        if (!isRefreashing) 
          break; 
        //headView의 상태가 READY 상태라면, 터치를 떼면 REFRESHING 상태로 들어가야 합니다 
        if (mHeaderView.getStatus() == SRefreshHeader.STATE_READY) { 
          mHeaderView.setState(SRefreshHeader.STATE_REFRESHING); 
          if (mOnRefreshListener != null) 
            mOnRefreshListener.onRefresh(); 
        } 
        //현재 상태에 따라 SrefreshLayout의 현재 인스턴스와 headView의 높이를 초기화합니다 
        resetHeadView(mHeaderView.getStatus()); 
        reset(mHeaderView.getStatus()); 
        mLastY = -1;//좌표 초기화 
        break; 
      case MotionEvent.ACTION_MOVE: 
        if (!isRefreashing) 
          isRefreashing = true; 
        final float deltaY = ev.getRawY(); - mLastY; 
        mLastY = ev.getRawY(); 
        updateHeaderViewHeight(deltaY / 1.8f);//이동 거리를 일정한 비율로 축소합니다 
        updateHeight(); 
        break; 
    } 
    return super.onTouchEvent(ev); 
  } 
  private void reset(int status) { 
    ViewGroup.LayoutParams lp = getLayoutParams(); 
    switch (status) { 
      case SRefreshHeader.STATE_REFRESHING: 
        lp.height = mHeight + mHeaderViewHeight; 
        break; 
      case SRefreshHeader.STATE_NORMAL: 
        lp.height = mHeight; 
        break; 
    } 
    setLayoutParams(lp); 
  } 
  private void resetHeadView(int status) { 
    switch (status) { 
      case SRefreshHeader.STATE_REFRESHING: 
        mHeaderView.setVisiableHeight(mHeaderViewHeight); 
        break; 
      case SRefreshHeader.STATE_NORMAL: 
        mHeaderView.setVisiableHeight(0); 
        break; 
    } 
  } 
  private void updateHeight() { 
    ViewGroup.LayoutParams lp = getLayoutParams(); 
    //현재 레이아웃 인스턴스 높이를 헤더뷰 높이와 초기 레이아웃 높이를 더한 값을 설정합니다 
    //만약 레이아웃을 업데이트하지 않으면 내용 높이가 압축되어 비율을 유지할 수 없습니다 
    lp.height = (mHeight + mHeaderView.getVisiableHeight()); 
    setLayoutParams(lp); 
  } 
  private void updateHeaderViewHeight(float space) { 
//    if (space < 0) 
//      space = 0; 
//    int factHeight = (int) (space - mHeaderViewHeight); 
    if (mHeaderView.getStatus() != SRefreshHeader.STATE_REFRESHING) 
      //만약 새로고침 중이 아니고 높이 
      if (mHeaderView.getVisiableHeight() < mHeaderViewHeight) * 2 && mHeaderView.getStatus() != SRefreshHeader.STATE_NORMAL) 
        mHeaderView.setState(SRefreshHeader.STATE_NORMAL); 
      } 
      if (mHeaderView.getVisiableHeight() > mHeaderViewHeight * 2 && mHeaderView.getStatus() != SRefreshHeader.STATE_READY) { 
        mHeaderView.setState(SRefreshHeader.STATE_READY); 
      } 
    } 
    mHeaderView.setVisiableHeight((int) space 
        + mHeaderView.getVisiableHeight()); 
  } 
  public void stopRefresh() { 
    if (mHeaderView.getStatus() == SRefreshHeader.STATE_REFRESHING) { 
      mHeaderView.setState(SRefreshHeader.STATE_NORMAL); 
      resetHeadView(SRefreshHeader.STATE_NORMAL); 
      reset(SRefreshHeader.STATE_NORMAL); 
    } 
  } 
  public void setOnRefreshListener(OnRefreshListener onRefreshListener) { 
    this.mOnRefreshListener = onRefreshListener; 
  } 
} 
public class SRefreshHeader extends LinearLayout { 
  private LinearLayout mContainer; 
  private int mState = STATE_NORMAL; 
  private Animation mRotateUpAnim; 
  private Animation mRotateDownAnim; 
  private final int ROTATE_ANIM_DURATION = 500; 
  public final static int STATE_NORMAL = 0;//下拉 갱신 
  public final static int STATE_READY = 1;//갱신 해제 
  public final static int STATE_REFRESHING = 2;//갱신 중 
  private ImageView mHeadArrowImage; 
  private TextView mHeadLastRefreashTimeTxt; 
  private TextView mHeadHintTxt; 
  private TextView mHeadLastRefreashTxt; 
  private ProgressBar mRefreshingProgress; 
  public SRefreshHeader(Context context) { 
    super(context); 
    initView(context); 
  } 
  /** 
   * @param context 
   * @param attrs 
   */ 
  public SRefreshHeader(Context context, AttributeSet attrs) { 
    super(context, attrs); 
    initView(context); 
  } 
  private void initView(Context context) { 
    // 초기 상태에서,下拉刷新view의 높이를 0으로 설정합니다 
    LayoutParams lp = new LayoutParams(LayoutParams.MATCH_PARENT, 0); 
    mContainer = (LinearLayout) LayoutInflater.from(context).inflate(R.layout.listview_head_view_layout, null); 
    addView(mContainer, lp); 
    setGravity(Gravity.BOTTOM); 
    mHeadArrowImage = (ImageView) findViewById(R.id.slistview_header_arrow); 
    mHeadLastRefreashTimeTxt = (TextView) findViewById(R.id.slistview_header_time); 
    mHeadHintTxt = (TextView) findViewById(R.id.slistview_header_hint_text); 
    mHeadLastRefreashTxt = (TextView) findViewById(R.id.slistview_header_last_refreash_txt); 
    mRefreshingProgress = (ProgressBar) findViewById(R.id.slistview_header_progressbar); 
    mRotateUpAnim = new RotateAnimation(0.0f, -180.0f, 
        Animation.RELATIVE_TO_SELF, 0.5f, Animation.RELATIVE_TO_SELF, 
        0.5f); 
    mRotateUpAnim.setDuration(ROTATE_ANIM_DURATION); 
    mRotateUpAnim.setFillAfter(true); 
    mRotateDownAnim = new RotateAnimation(-180.0f, 0.0f, 
        Animation.RELATIVE_TO_SELF, 0.5f, Animation.RELATIVE_TO_SELF, 
        0.5f); 
    mRotateDownAnim.setDuration(ROTATE_ANIM_DURATION); 
    mRotateDownAnim.setFillAfter(true); 
  } 
  public void setState(int state) { 
    if (state == mState) return; 
    if (state == STATE_REFRESHING) {  // 显示进度 
      mHeadArrowImage.clearAnimation(); 
      mHeadArrowImage.setVisibility(View.INVISIBLE); 
      mRefreshingProgress.setVisibility(View.VISIBLE); 
    } else {  // 显示箭头图片 
      mHeadArrowImage.setVisibility(View.VISIBLE); 
      mRefreshingProgress.setVisibility(View.INVISIBLE); 
    } 
    switch (state) { 
      case STATE_NORMAL: 
        if (mState == STATE_READY) { 
          mHeadArrowImage.startAnimation(mRotateDownAnim); 
        } 
        if (mState == STATE_REFRESHING) { 
          mHeadArrowImage.clearAnimation(); 
        } 
        mHeadHintTxt.setText("下拉刷新"); 
        break; 
      case STATE_READY: 
        if (mState != STATE_READY) { 
          mHeadArrowImage.clearAnimation(); 
          mHeadArrowImage.startAnimation(mRotateUpAnim); 
          mHeadHintTxt.setText("손을 떼어 업데이트"); 
        } 
        break; 
      case STATE_REFRESHING: 
        mHeadHintTxt.setText("업데이트 중입니다"); 
        break; 
      default: 
    } 
    mState = state; 
  } 
  public void setVisiableHeight(int height) { 
    if (height < 0) 
      height = 0; 
    LayoutParams lp = (LayoutParams) mContainer 
        .getLayoutParams(); 
    lp.height = height; 
    mContainer.setLayoutParams(lp); 
  } 
  public int getStatus() { 
    return mState; 
  } 
  public int getVisiableHeight() { 
    return mContainer.getHeight(); 
  } 
} 

최종으로는 레이아웃 파일

<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android" 
  android:layout_width="match_parent" 
  android:layout_height="wrap_content" 
  android:gravity="bottom"> 
  <RelativeLayout 
    android:id="@"+id/slistview_header_content" 
    android:layout_width="match_parent" 
    android:layout_height="60dp"> 
    <LinearLayout 
      android:id="@"+id/slistview_header_text" 
      android:layout_width="wrap_content" 
      android:layout_height="wrap_content" 
      android:layout_centerInParent="true" 
      android:gravity="center" 
      android:orientation="vertical"> 
      <TextView 
        android:id="@"+id/slistview_header_hint_text" 
        android:layout_width="wrap_content" 
        android:layout_height="wrap_content" 
        android:text="下拉 업데이트" /> 
      <LinearLayout 
        android:layout_width="wrap_content" 
        android:layout_height="wrap_content" 
        android:layout_marginTop="3dp"> 
        <TextView 
          android:id="@"+id/slistview_header_last_refreash_txt" 
          android:layout_width="wrap_content" 
          android:layout_height="wrap_content" 
          android:text="지난 업데이트 시간" 
          android:textSize="12sp" /> 
        <TextView 
          android:id="@"+id/slistview_header_time" 
          android:layout_width="wrap_content" 
          android:layout_height="wrap_content" 
          android:textSize="12sp" /> 
      </LinearLayout> 
    </LinearLayout> 
    <ProgressBar 
      android:id="@"+id/slistview_header_progressbar" 
      android:layout_width="30dp" 
      android:layout_height="30dp" 
      android:layout_centerVertical="true" 
      android:layout_toLeftOf="@id/slistview_header_text" 
      android:visibility="invisible" /> 
    <ImageView 
      android:id="@"+id/slistview_header_arrow" 
      android:layout_width="wrap_content" 
      android:layout_height="wrap_content" 
      android:layout_alignLeft="@id/slistview_header_progressbar" 
      android:layout_centerVertical="true" 
      android:layout_toLeftOf="@id/slistview_header_text" 
      android:src="@drawable/mmtlistview_arrow" /> 
  </RelativeLayout> 
</LinearLayout> 

이것이 이 문서의 전체 내용입니다. 여러분의 학습에 도움이 되길 바라며, 또한 다른 사람들도 지지해 주시길 바랍니다.

선언: 이 문서의 내용은 인터넷에서 가져왔으며, 저작권자는 모두 소유합니다. 내용은 인터넷 사용자가 자발적으로 기여하고 업로드한 것이며, 이 사이트는 소유권을 가지지 않으며, 인공적인 편집 처리를 하지 않았으며, 관련 법적 책임도 부담하지 않습니다. 저작권 침해가 의심되는 내용을 발견하시면, notice#w로 이메일을 보내 주시기 바랍니다.3codebox.com(보고할 때는 #을 @으로 변경하십시오. 신고하고 관련 증거를 제공하시면, 사실이 확인되면 이 사이트는 즉시 저작권 침해 내용을 삭제합니다。)

추천