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

Implementation of Android RefreshLayout for Pull-to-Refresh Layout

The project needs the function of pull to refresh, but this View is not a ListView or such control, it needs ViewGroup to implement this function. At first, I looked around online roughly and didn't find a particularly suitable one, and the code was also not easy to understand, so I decided to write one myself.

  So I dug out the source code of XlistView and looked at it piece by piece, and after a general understanding of the Xlistview source code, I finally decided to do it myself

  For convenience, the headView still used XListView's HeadView, which saved a lot of trouble:(

  Pull to refresh, pull to refresh, it's definitely to implement the pull-down function first. Initially, I planned to implement it through extends ScrollView, because there is a ready-made scroll effect, but in actual fact, I gave up for two reasons:

1In the ScrollView, there can only be one child View. Although you can add a ViewGroup under Scroll and dynamically add headView into the front ViewGroup, I am still more accustomed to the visual preview of studio, and it always feels not intuitive!

2When a ListView is nested within a ScrollView, there will be a conflict, and it is also necessary to override the ListView. So, I gave up and changed my mind!

 Regarding the above reason1Dynamicly add headView into the ScrollView's GroupView, you can override the ScrollView's onViewAdded() method to add the initialized headView into the child GroupView

@Override 
public void onViewAdded(View child) { 
  super.onViewAdded(child); 
  //Since the headView needs to be on top, the first thing that comes to mind is a Vertical LinearLayout 
  LinearLayout linearLayout = (LinearLayout) getChildAt(0); 
  linearLayout.addView(view, 0); 
} 

  Let's change our perspective and implement it by extending LinearLayout!
First, do the preparation work. We need a HeaderView and the height of the HeaderView to be obtained, as well as the initial height of the 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 is instantiated through the constructor

mHeaderViewContent = (RelativeLayout)

mHeaderView.findViewById(R.id.slistview_header_content);

 This is to parse the content area of the headerView, and we will need to obtain the height of this view later. You may wonder why not use the mHeaderView above to obtain the height. You can see the following code by entering the constructor

// Set the height of the pull-down refresh view to 0 in the initial state 
LayoutParams lp = new LayoutParams(LayoutParams.MATCH_PARENT, 0); 
mContainer = (LinearLayout) LayoutInflater.from(context).inflate(R.layout.listview_head_view_layout, null); 
w(mContainer, lp); 

If you directly obtain the height of mHeaderView, it must be 0
getHeaderViewHeight();
getViewHeight();
These are to obtain the height of the HeaderView and the initial height of the Layout

/** 
  * Get the height of headView 
  */ 
  private void getHeaderViewHeight() { 
    ViewTreeObserver vto2 = mHeaderViewContent.getViewTreeObserver(); 
    vto2.addOnGlobalLayoutListener(new ViewTreeObserver.OnGlobalLayoutListener() { 
      @Override 
      public void onGlobalLayout() { 
        mHeaderViewHeight = mHeaderViewContent.getHeight(); 
        mHeaderViewContent.getViewTreeObserver().removeGlobalOnLayoutListener(this); 
      } 
    }); 
  } 
  /** 
  * Get the current height of SRefreshLayout instance 
  */ 
  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); 
      } 
    }); 
  } 

The preparation work is done, and the next step is to perform the pull-down operation
By now, you must have thought of the onTouchListener() method, that's right! Let's start working here now

To implement the pull-down, it will go through three processes in total
ACTION_UP→ACTION_MOVE→ACTION_UP
In the ACTION_UP event, that is, when the finger is pressed down, all we need to do is record the coordinate at the moment of pressing

switch (ev.getAction()) { 
      case MotionEvent.ACTION_DOWN: 
        //Record the starting height 
        mLastY = ev.getRawY();//Record the Y coordinate at the time of pressing 
        break; 

Then it's the ACTION_MOVE event, which is the most important, because the height changes of HeadView and Layout during the pull-down are all handled here

case MotionEvent.ACTION_MOVE: 
       if (!isRefreashing) 
         isRefreashing = true; 
       final float deltaY = ev.getRawY(); - mLastY; 
       mLastY = ev.getRawY(); 
       updateHeaderViewHeight(deltaY / 1.8f);//Shrink the moving distance by a certain proportion 
       updateHeight(); 
       break; 

The updateHeaderViewHeight and updateHeight are respectively used to change the height of HeaderView and Layout

 private void updateHeight() { 
    ViewGroup.LayoutParams lp = getLayoutParams(); 
    //Update the current layout instance height to the header view height plus the initial layout height 
    //If not updating the layout, the content height will be compressed, unable to maintain the proportion 
    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 not in the refresh state and if the height 
      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()); 
  } 

When updating the Header height, we judge whether the pull-down distance has reached the refresh distance. In the above code, I set it to twice the initial height of mHeaderView to enter the 'release to refresh' state, and if it has not reached, it remains in the 'pull-to-refresh' state
A total of states are set for the status in HeaderView3The two are

public final static int STATE_NORMAL = 0;//Pull to refresh 
 public final static int STATE_READY = 1;//Release refresh 
 public final static int STATE_REFRESHING = 2;//Refreshing 

The method to update the height is the same for both headerView and layout, which is to add the distance moved to the original height and then assign it back to headerView or layout

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

Finally, it's the ACTION_UP event, which is when the finger leaves the screen. Here, we need to decide the final state of headerView based on its current state!

case MotionEvent.ACTION_UP: 
        //When released 
        //Avoid triggering click events 
        if (!isRefreashing) 
          break; 
        //If the headView status is in the READY state, it means that when released, it should enter the REFRESHING state 
        if (mHeaderView.getStatus() == SRefreshHeader.STATE_READY) { 
          mHeaderView.setState(SRefreshHeader.STATE_REFRESHING); 
        } 
        //Reset the current instance and headView height of SrefreshLayout according to the status 
        resetHeadView(mHeaderView.getStatus()); 
        reset(mHeaderView.getStatus()); 
        mLastY = -1;//Reset coordinates 
        break; 

resetHeadView and reset are methods to reset the height of headerView and layout respectively

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; 
    } 
  } 

The implementation method is the same. According to the status, if it is in the refreshing state, the headerView should be displayed normally with the initial height. If it is in NORMAL, that is, the 'pull-to-refresh' state, it means that the refresh has not been triggered, and the headerView should be hidden, that is, the height should be reset to 0

The pull-to-refresh operation is basically done here. We still need to add a callback interface for notification

interface OnRefreshListener { 
    void onRefresh(); 
  } 
case MotionEvent.ACTION_UP: 
        //When released 
        //Avoid triggering click events 
        if (!isRefreashing) 
          break; 
        //If the headView status is in the READY state, it means that when released, it should enter the REFRESHING state 
        if (mHeaderView.getStatus() == SRefreshHeader.STATE_READY) { 
          mHeaderView.setState(SRefreshHeader.STATE_REFRESHING); 
          if (mOnRefreshListener != null) 
            mOnRefreshListener.onRefresh(); 
        } 
        //Reset the current instance and headView height of SrefreshLayout according to the status 
        resetHeadView(mHeaderView.getStatus()); 
        reset(mHeaderView.getStatus()); 
        mLastY = -1;//Reset coordinates 
        break; 

Alright, it's basically done up to here. Try the effect. Huh, I found a problem. Why can't this Layout perform pull-to-refresh when nested in a ListView? Thinking carefully, it should be a problem with event dispatching, and we need to handle the event interception!
Regarding the event interception, I read the blog about ViewGroup event dispatching written by the great god Hongyang and Android-Ultra-Pull-To-Found the solution in the source code of the Refresh part:

@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("Found 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("Intercepting successfully"); 
          return true; 
        } else { 
          Logs.v("Not intercepting"); 
          return false; 
        } 
    } 
    return super.onInterceptTouchEvent(ev); 
  } 

Among them

if (space > 0 && !absListView.canScrollVertically(-1) && absListView.getFirstVisiblePosition() == 0)
space represents the distance moved; canScrollVertically() is used to determine if the ListView can scroll vertically. A negative parameter indicates scrolling upwards, while a positive parameter indicates scrolling downwards. The last one represents the position of the first visible item in the ListView.

With the above event interception processing, a Viewgroup that meets the requirements mentioned at the beginning is completed!

The source code of Layout and HeaderView (directly using the HeaderView of XlistView) is attached below.

public class SRefreshLayout extends LinearLayout { 
  private SRefreshHeader mHeaderView; 
  private RelativeLayout mHeaderViewContent; 
  private boolean isRefreashing; 
  private float mLastY = -1;//initial press height 
  private int mHeaderViewHeight;//header view content height 
  private int mHeight;//layout height 
  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(); 
  } 
  /** 
   * Get the height of headView 
   */ 
  private void getHeaderViewHeight() { 
    ViewTreeObserver vto2 = mHeaderViewContent.getViewTreeObserver(); 
    vto2.addOnGlobalLayoutListener(new ViewTreeObserver.OnGlobalLayoutListener() { 
      @Override 
      public void onGlobalLayout() { 
        mHeaderViewHeight = mHeaderViewContent.getHeight(); 
        mHeaderViewContent.getViewTreeObserver().removeGlobalOnLayoutListener(this); 
      } 
    }); 
  } 
  /** 
   * Get the current height of SRefreshLayout instance 
   */ 
  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("Found 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("Intercepting successfully"); 
          return true; 
        } else { 
          Logs.v("Not intercepting"); 
          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: 
        //Record the starting height 
        mLastY = ev.getRawY();//Record the Y coordinate at the time of pressing 
        break; 
      //When the finger leaves the screen 
      case MotionEvent.ACTION_UP: 
        //When released 
        //Avoid triggering click events 
        if (!isRefreashing) 
          break; 
        //If the headView status is in the READY state, it means that when released, it should enter the REFRESHING state 
        if (mHeaderView.getStatus() == SRefreshHeader.STATE_READY) { 
          mHeaderView.setState(SRefreshHeader.STATE_REFRESHING); 
          if (mOnRefreshListener != null) 
            mOnRefreshListener.onRefresh(); 
        } 
        //Reset the current instance and headView height of SrefreshLayout according to the status 
        resetHeadView(mHeaderView.getStatus()); 
        reset(mHeaderView.getStatus()); 
        mLastY = -1;//Reset coordinates 
        break; 
      case MotionEvent.ACTION_MOVE: 
        if (!isRefreashing) 
          isRefreashing = true; 
        final float deltaY = ev.getRawY(); - mLastY; 
        mLastY = ev.getRawY(); 
        updateHeaderViewHeight(deltaY / 1.8f);//Shrink the moving distance by a certain proportion 
        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(); 
    //Update the current layout instance height to the header view height plus the initial layout height 
    //If not updating the layout, the content height will be compressed, unable to maintain the proportion 
    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 not in the refresh state and if the height 
      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;//Pull to refresh 
  public final static int STATE_READY = 1;//Release refresh 
  public final static int STATE_REFRESHING = 2;//Refreshing 
  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) { 
    // Set the height of the pull-down refresh view to 0 in the initial state 
    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) {  // Display progress 
      mHeadArrowImage.clearAnimation(); 
      mHeadArrowImage.setVisibility(View.INVISIBLE); 
      mRefreshingProgress.setVisibility(View.VISIBLE); 
    } else {  // Display arrow image 
      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("Pull to Refresh"); 
        break; 
      case STATE_READY: 
        if (mState != STATE_READY) { 
          mHeadArrowImage.clearAnimation(); 
          mHeadArrowImage.startAnimation(mRotateUpAnim); 
          mHeadHintTxt.setText("Release to Refresh"); 
        } 
        break; 
      case STATE_REFRESHING: 
        mHeadHintTxt.setText("Refreshing..."); 
        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(); 
  } 
} 

The last is the layout file

<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="Pull to Refresh" /> 
      <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="Last Refresh Time" 
          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> 

That's all for this article. I hope it will be helpful to everyone's learning, and I also hope everyone will support the呐喊 tutorial more.

Statement: The content of this article is from the network, and the copyright belongs to the original author. The content is contributed and uploaded by Internet users spontaneously. This website does not own the copyright, has not been manually edited, and does not bear relevant legal liability. If you find any content suspected of copyright infringement, please send an email to: notice#oldtoolbag.com (When reporting via email, please replace # with @) for reporting, and provide relevant evidence. Once verified, this site will immediately delete the infringing content.

You May Also Like