Android XListView实现原理讲解及分析

【Android界面实现】XListView实现原理讲解及分析 转载请注明出处:http://blog.youkuaiyun.com/zhaokaiqiang1992    XListview是一个非常受欢迎的下拉刷新控件,但是已经停止维护了。之前写过一篇XListview的使用介绍,用起来非常简单,这两天放假无聊,研究了下XListview的实现原理,学到了很多,今天分享给大家。    提前声明,为了让代码更好的理解,我对代码进行了部分删减和重构,如果大家想看原版代码, 阅读详情

   

转载自:http://blog.youkuaiyun.com/zhaokaiqiang1992/article/details/42392731

XListview是一个非常受欢迎的下拉刷新控件,但是已经停止维护了。之前写过一篇XListview的使用介绍,用起来非常简单,这两天放假无聊,研究了下XListview的实现原理,学到了很多,今天分享给大家。

    提前声明,为了让代码更好的理解,我对代码进行了部分删减和重构,如果大家想看原版代码,请去github自行下载。

    Xlistview项目主要是三部分:XlistView,XListViewHeader,XListViewFooter,分别是XListView主体、header、footer的实现。下面我们分开来介绍。

    下面是修改之后的XListViewHeader代码

[java]  view plain copy
  1. public class XListViewHeader extends LinearLayout {  
  2.   
  3.     private static final String HINT_NORMAL = "下拉刷新";  
  4.     private static final String HINT_READY = "松开刷新数据";  
  5.     private static final String HINT_LOADING = "正在加载...";  
  6.   
  7.     // 正常状态  
  8.     public final static int STATE_NORMAL = 0;  
  9.     // 准备刷新状态,也就是箭头方向发生改变之后的状态  
  10.     public final static int STATE_READY = 1;  
  11.     // 刷新状态,箭头变成了progressBar  
  12.     public final static int STATE_REFRESHING = 2;  
  13.     // 布局容器,也就是根布局  
  14.     private LinearLayout container;  
  15.     // 箭头图片  
  16.     private ImageView mArrowImageView;  
  17.     // 刷新状态显示  
  18.     private ProgressBar mProgressBar;  
  19.     // 说明文本  
  20.     private TextView mHintTextView;  
  21.     // 记录当前的状态  
  22.     private int mState;  
  23.     // 用于改变箭头的方向的动画  
  24.     private Animation mRotateUpAnim;  
  25.     private Animation mRotateDownAnim;  
  26.     // 动画持续时间  
  27.     private final int ROTATE_ANIM_DURATION = 180;  
  28.   
  29.     public XListViewHeader(Context context) {  
  30.         super(context);  
  31.         initView(context);  
  32.     }  
  33.   
  34.     public XListViewHeader(Context context, AttributeSet attrs) {  
  35.         super(context, attrs);  
  36.         initView(context);  
  37.     }  
  38.   
  39.     private void initView(Context context) {  
  40.         mState = STATE_NORMAL;  
  41.         // 初始情况下,设置下拉刷新view高度为0  
  42.         LinearLayout.LayoutParams lp = new LinearLayout.LayoutParams(  
  43.                 LayoutParams.MATCH_PARENT, 0);  
  44.         container = (LinearLayout) LayoutInflater.from(context).inflate(  
  45.                 R.layout.xlistview_header, null);  
  46.         addView(container, lp);  
  47.         // 初始化控件  
  48.         mArrowImageView = (ImageView) findViewById(R.id.xlistview_header_arrow);  
  49.         mHintTextView = (TextView) findViewById(R.id.xlistview_header_hint_textview);  
  50.         mProgressBar = (ProgressBar) findViewById(R.id.xlistview_header_progressbar);  
  51.         // 初始化动画  
  52.         mRotateUpAnim = new RotateAnimation(0.0f, -180.0f,  
  53.                 Animation.RELATIVE_TO_SELF, 0.5f, Animation.RELATIVE_TO_SELF,  
  54.                 0.5f);  
  55.         mRotateUpAnim.setDuration(ROTATE_ANIM_DURATION);  
  56.         mRotateUpAnim.setFillAfter(true);  
  57.         mRotateDownAnim = new RotateAnimation(-180.0f, 0.0f,  
  58.                 Animation.RELATIVE_TO_SELF, 0.5f, Animation.RELATIVE_TO_SELF,  
  59.                 0.5f);  
  60.         mRotateDownAnim.setDuration(ROTATE_ANIM_DURATION);  
  61.         mRotateDownAnim.setFillAfter(true);  
  62.     }  
  63.   
  64.     // 设置header的状态  
  65.     public void setState(int state) {  
  66.         if (state == mState)  
  67.             return;  
  68.   
  69.         // 显示进度  
  70.         if (state == STATE_REFRESHING) {  
  71.             mArrowImageView.clearAnimation();  
  72.             mArrowImageView.setVisibility(View.INVISIBLE);  
  73.             mProgressBar.setVisibility(View.VISIBLE);  
  74.         } else {  
  75.             // 显示箭头  
  76.             mArrowImageView.setVisibility(View.VISIBLE);  
  77.             mProgressBar.setVisibility(View.INVISIBLE);  
  78.         }  
  79.   
  80.         switch (state) {  
  81.         case STATE_NORMAL:  
  82.             if (mState == STATE_READY) {  
  83.                 mArrowImageView.startAnimation(mRotateDownAnim);  
  84.             }  
  85.             if (mState == STATE_REFRESHING) {  
  86.                 mArrowImageView.clearAnimation();  
  87.             }  
  88.             mHintTextView.setText(HINT_NORMAL);  
  89.             break;  
  90.         case STATE_READY:  
  91.             if (mState != STATE_READY) {  
  92.                 mArrowImageView.clearAnimation();  
  93.                 mArrowImageView.startAnimation(mRotateUpAnim);  
  94.                 mHintTextView.setText(HINT_READY);  
  95.             }  
  96.             break;  
  97.         case STATE_REFRESHING:  
  98.             mHintTextView.setText(HINT_LOADING);  
  99.             break;  
  100.         }  
  101.   
  102.         mState = state;  
  103.     }  
  104.   
  105.     public void setVisiableHeight(int height) {  
  106.         if (height < 0)  
  107.             height = 0;  
  108.         LinearLayout.LayoutParams lp = (LinearLayout.LayoutParams) container  
  109.                 .getLayoutParams();  
  110.         lp.height = height;  
  111.         container.setLayoutParams(lp);  
  112.     }  
  113.   
  114.     public int getVisiableHeight() {  
  115.         return container.getHeight();  
  116.     }  
  117.   
  118.     public void show() {  
  119.         container.setVisibility(View.VISIBLE);  
  120.     }  
  121.   
  122.     public void hide() {  
  123.         container.setVisibility(View.INVISIBLE);  
  124.     }  
  125.   
  126. }  

    XListViewHeader继承自linearLayout,用来实现下拉刷新时的界面展示,可以分为三种状态:正常、准备刷新、正在加载。

    在Linearlayout布局里面,主要有指示箭头、说明文本、圆形加载条三个控件。在构造函数中,调用了initView()进行控件的初始化操作。在添加布局文件的时候,指定高度为0,这是为了隐藏header,然后初始化动画,是为了完成箭头的旋转动作。

    setState()是设置header的状态,因为header需要根据不同的状态,完成控件隐藏、显示、改变文字等操作,这个方法主要是在XListView里面调用。除此之外,还有setVisiableHeight()和getVisiableHeight(),这两个方法是为了设置和获取Header中根布局文件的高度属性,从而完成拉伸和收缩的效果,而show()和hide()则显然就是完成显示和隐藏的效果。

    下面是Header的布局文件

[html]  view plain copy
  1. <?xml version="1.0" encoding="utf-8"?>  
  2. <LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"  
  3.     xmlns:tools="http://schemas.android.com/tools"  
  4.     android:layout_width="match_parent"  
  5.     android:layout_height="wrap_content"  
  6.     android:gravity="bottom" >  
  7.   
  8.     <RelativeLayout  
  9.         android:id="@+id/xlistview_header_content"  
  10.         android:layout_width="match_parent"  
  11.         android:layout_height="60dp"  
  12.         tools:ignore="UselessParent" >  
  13.   
  14.         <TextView  
  15.             android:id="@+id/xlistview_header_hint_textview"  
  16.             android:layout_width="100dp"  
  17.             android:layout_height="wrap_content"  
  18.             android:layout_centerInParent="true"  
  19.             android:gravity="center"  
  20.             android:text="正在加载"  
  21.             android:textColor="@android:color/black"  
  22.             android:textSize="14sp" />  
  23.   
  24.         <ImageView  
  25.             android:id="@+id/xlistview_header_arrow"  
  26.             android:layout_width="30dp"  
  27.             android:layout_height="wrap_content"  
  28.             android:layout_centerVertical="true"  
  29.             android:layout_toLeftOf="@id/xlistview_header_hint_textview"  
  30.             android:src="@drawable/xlistview_arrow" />  
  31.   
  32.         <ProgressBar  
  33.             android:id="@+id/xlistview_header_progressbar"  
  34.             style="@style/progressbar_style"  
  35.             android:layout_width="30dp"  
  36.             android:layout_height="30dp"  
  37.             android:layout_centerVertical="true"  
  38.             android:layout_toLeftOf="@id/xlistview_header_hint_textview"  
  39.             android:visibility="invisible" />  
  40.     </RelativeLayout>  
  41.   
  42. </LinearLayout>  

    说完了Header,我们再看看Footer。Footer是为了完成加载更多功能时候的界面展示,基本思路和Header是一样的,下面是Footer的代码

[java]  view plain copy
  1. public class XListViewFooter extends LinearLayout {  
  2.   
  3.     // 正常状态  
  4.     public final static int STATE_NORMAL = 0;  
  5.     // 准备状态  
  6.     public final static int STATE_READY = 1;  
  7.     // 加载状态  
  8.     public final static int STATE_LOADING = 2;  
  9.   
  10.     private View mContentView;  
  11.     private View mProgressBar;  
  12.     private TextView mHintView;  
  13.   
  14.     public XListViewFooter(Context context) {  
  15.         super(context);  
  16.         initView(context);  
  17.     }  
  18.   
  19.     public XListViewFooter(Context context, AttributeSet attrs) {  
  20.         super(context, attrs);  
  21.         initView(context);  
  22.     }  
  23.   
  24.     private void initView(Context context) {  
  25.   
  26.         LinearLayout moreView = (LinearLayout) LayoutInflater.from(context)  
  27.                 .inflate(R.layout.xlistview_footer, null);  
  28.         addView(moreView);  
  29.         moreView.setLayoutParams(new LinearLayout.LayoutParams(  
  30.                 LayoutParams.MATCH_PARENT, LayoutParams.WRAP_CONTENT));  
  31.   
  32.         mContentView = moreView.findViewById(R.id.xlistview_footer_content);  
  33.         mProgressBar = moreView.findViewById(R.id.xlistview_footer_progressbar);  
  34.         mHintView = (TextView) moreView  
  35.                 .findViewById(R.id.xlistview_footer_hint_textview);  
  36.     }  
  37.   
  38.     /** 
  39.      * 设置当前的状态 
  40.      *  
  41.      * @param state 
  42.      */  
  43.     public void setState(int state) {  
  44.   
  45.         mProgressBar.setVisibility(View.INVISIBLE);  
  46.         mHintView.setVisibility(View.INVISIBLE);  
  47.   
  48.         switch (state) {  
  49.         case STATE_READY:  
  50.             mHintView.setVisibility(View.VISIBLE);  
  51.             mHintView.setText(R.string.xlistview_footer_hint_ready);  
  52.             break;  
  53.   
  54.         case STATE_NORMAL:  
  55.             mHintView.setVisibility(View.VISIBLE);  
  56.             mHintView.setText(R.string.xlistview_footer_hint_normal);  
  57.             break;  
  58.   
  59.         case STATE_LOADING:  
  60.             mProgressBar.setVisibility(View.VISIBLE);  
  61.             break;  
  62.   
  63.         }  
  64.   
  65.     }  
  66.   
  67.     public void setBottomMargin(int height) {  
  68.         if (height > 0) {  
  69.   
  70.             LinearLayout.LayoutParams lp = (LinearLayout.LayoutParams) mContentView  
  71.                     .getLayoutParams();  
  72.             lp.bottomMargin = height;  
  73.             mContentView.setLayoutParams(lp);  
  74.         }  
  75.     }  
  76.   
  77.     public int getBottomMargin() {  
  78.         LinearLayout.LayoutParams lp = (LinearLayout.LayoutParams) mContentView  
  79.                 .getLayoutParams();  
  80.         return lp.bottomMargin;  
  81.     }  
  82.   
  83.     public void hide() {  
  84.         LinearLayout.LayoutParams lp = (LinearLayout.LayoutParams) mContentView  
  85.                 .getLayoutParams();  
  86.         lp.height = 0;  
  87.         mContentView.setLayoutParams(lp);  
  88.     }  
  89.   
  90.     public void show() {  
  91.         LinearLayout.LayoutParams lp = (LinearLayout.LayoutParams) mContentView  
  92.                 .getLayoutParams();  
  93.         lp.height = LayoutParams.WRAP_CONTENT;  
  94.         mContentView.setLayoutParams(lp);  
  95.     }  
  96.   
  97. }  

    从上面的代码里面,我们可以看出,footer和header的思路是一样的,只不过,footer的拉伸和显示效果不是通过高度来模拟的,而是通过设置BottomMargin来完成的。

    下面是Footer的布局文件

[html]  view plain copy 在CODE上查看代码片 派生到我的代码片
  1. <?xml version="1.0" encoding="utf-8"?>  
  2. <LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"  
  3.     xmlns:tools="http://schemas.android.com/tools"  
  4.     android:layout_width="fill_parent"  
  5.     android:layout_height="wrap_content" >  
  6.   
  7.     <RelativeLayout  
  8.         android:id="@+id/xlistview_footer_content"  
  9.         android:layout_width="fill_parent"  
  10.         android:layout_height="wrap_content"  
  11.         android:padding="5dp"  
  12.         tools:ignore="UselessParent" >  
  13.   
  14.         <ProgressBar  
  15.             android:id="@+id/xlistview_footer_progressbar"  
  16.             style="@style/progressbar_style"  
  17.             android:layout_width="30dp"  
  18.             android:layout_height="30dp"  
  19.             android:layout_centerInParent="true"  
  20.             android:visibility="invisible" />  
  21.   
  22.         <TextView  
  23.             android:id="@+id/xlistview_footer_hint_textview"  
  24.             android:layout_width="wrap_content"  
  25.             android:layout_height="wrap_content"  
  26.             android:layout_centerInParent="true"  
  27.             android:text="@string/xlistview_footer_hint_normal"  
  28.             android:textColor="@android:color/black"  
  29.             android:textSize="14sp" />  
  30.     </RelativeLayout>  
  31.   
  32. </LinearLayout>  

    在了解了Header和footer之后,我们就要介绍最核心的XListView的代码实现了。

    在介绍代码实现之前,我先介绍一下XListView的实现原理。

    首先,一旦使用XListView,Footer和Header就已经添加到我们的ListView上面了,XListView就是通过继承ListView,然后处理了屏幕点击事件和控制滑动实现效果的。所以,如果我们的Adapter中getCount()返回的值是20,那么其实XListView里面是有20+2个item的,这个数量即使我们关闭了XListView的刷新和加载功能,也是不会变化的。Header和Footer通过addHeaderView和addFooterView添加上去之后,如果想实现下拉刷新和上拉加载功能,那么就必须有拉伸效果,所以就像上面的那样,Header是通过设置height,Footer是通过设置BottomMargin来模拟拉伸效果。那么回弹效果呢?仅仅通过设置高度或者是间隔是达不到模拟回弹效果的,因此,就需要用Scroller来实现模拟回弹效果。在说明原理之后,我们开始介绍XListView的核心实现原理。

    再次提示,下面的代码经过我重构了,只是为了看起来更好的理解。

[java]  view plain copy 在CODE上查看代码片 派生到我的代码片
  1. public class XListView extends ListView {  
  2.   
  3.     private final static int SCROLLBACK_HEADER = 0;  
  4.     private final static int SCROLLBACK_FOOTER = 1;  
  5.     // 滑动时长  
  6.     private final static int SCROLL_DURATION = 400;  
  7.     // 加载更多的距离  
  8.     private final static int PULL_LOAD_MORE_DELTA = 100;  
  9.     // 滑动比例  
  10.     private final static float OFFSET_RADIO = 2f;  
  11.     // 记录按下点的y坐标  
  12.     private float lastY;  
  13.     // 用来回滚  
  14.     private Scroller scroller;  
  15.     private IXListViewListener mListViewListener;  
  16.     private XListViewHeader headerView;  
  17.     private RelativeLayout headerViewContent;  
  18.     // header的高度  
  19.     private int headerHeight;  
  20.     // 是否能够刷新  
  21.     private boolean enableRefresh = true;  
  22.     // 是否正在刷新  
  23.     private boolean isRefreashing = false;  
  24.     // footer  
  25.     private XListViewFooter footerView;  
  26.     // 是否可以加载更多  
  27.     private boolean enableLoadMore;  
  28.     // 是否正在加载  
  29.     private boolean isLoadingMore;  
  30.     // 是否footer准备状态  
  31.     private boolean isFooterAdd = false;  
  32.     // total list items, used to detect is at the bottom of listview.  
  33.     private int totalItemCount;  
  34.     // 记录是从header还是footer返回  
  35.     private int mScrollBack;  
  36.   
  37.     private static final String TAG = "XListView";  
  38.   
  39.     public XListView(Context context) {  
  40.         super(context);  
  41.         initView(context);  
  42.     }  
  43.   
  44.     public XListView(Context context, AttributeSet attrs) {  
  45.         super(context, attrs);  
  46.         initView(context);  
  47.     }  
  48.   
  49.     public XListView(Context context, AttributeSet attrs, int defStyle) {  
  50.         super(context, attrs, defStyle);  
  51.         initView(context);  
  52.     }  
  53.   
  54.     private void initView(Context context) {  
  55.   
  56.         scroller = new Scroller(context, new DecelerateInterpolator());  
  57.   
  58.         headerView = new XListViewHeader(context);  
  59.         footerView = new XListViewFooter(context);  
  60.   
  61.         headerViewContent = (RelativeLayout) headerView  
  62.                 .findViewById(R.id.xlistview_header_content);  
  63.         headerView.getViewTreeObserver().addOnGlobalLayoutListener(  
  64.                 new OnGlobalLayoutListener() {  
  65.                     @SuppressWarnings("deprecation")  
  66.                     @Override  
  67.                     public void onGlobalLayout() {  
  68.                         headerHeight = headerViewContent.getHeight();  
  69.                         getViewTreeObserver()  
  70.                                 .removeGlobalOnLayoutListener(this);  
  71.                     }  
  72.                 });  
  73.         addHeaderView(headerView);  
  74.   
  75.     }  
  76.   
  77.     @Override  
  78.     public void setAdapter(ListAdapter adapter) {  
  79.         // 确保footer最后添加并且只添加一次  
  80.         if (isFooterAdd == false) {  
  81.             isFooterAdd = true;  
  82.             addFooterView(footerView);  
  83.         }  
  84.         super.setAdapter(adapter);  
  85.   
  86.     }  
  87.   
  88.     @Override  
  89.     public boolean onTouchEvent(MotionEvent ev) {  
  90.   
  91.         totalItemCount = getAdapter().getCount();  
  92.         switch (ev.getAction()) {  
  93.         case MotionEvent.ACTION_DOWN:  
  94.             // 记录按下的坐标  
  95.             lastY = ev.getRawY();  
  96.             break;  
  97.         case MotionEvent.ACTION_MOVE:  
  98.             // 计算移动距离  
  99.             float deltaY = ev.getRawY() - lastY;  
  100.             lastY = ev.getRawY();  
  101.             // 是第一项并且标题已经显示或者是在下拉  
  102.             if (getFirstVisiblePosition() == 0  
  103.                     && (headerView.getVisiableHeight() > 0 || deltaY > 0)) {  
  104.                 updateHeaderHeight(deltaY / OFFSET_RADIO);  
  105.             } else if (getLastVisiblePosition() == totalItemCount - 1  
  106.                     && (footerView.getBottomMargin() > 0 || deltaY < 0)) {  
  107.                 updateFooterHeight(-deltaY / OFFSET_RADIO);  
  108.             }  
  109.             break;  
  110.   
  111.         case MotionEvent.ACTION_UP:  
  112.   
  113.             if (getFirstVisiblePosition() == 0) {  
  114.                 if (enableRefresh  
  115.                         && headerView.getVisiableHeight() > headerHeight) {  
  116.                     isRefreashing = true;  
  117.                     headerView.setState(XListViewHeader.STATE_REFRESHING);  
  118.                     if (mListViewListener != null) {  
  119.                         mListViewListener.onRefresh();  
  120.                     }  
  121.                 }  
  122.                 resetHeaderHeight();  
  123.             } else if (getLastVisiblePosition() == totalItemCount - 1) {  
  124.                 if (enableLoadMore  
  125.                         && footerView.getBottomMargin() > PULL_LOAD_MORE_DELTA) {  
  126.                     startLoadMore();  
  127.                 }  
  128.                 resetFooterHeight();  
  129.             }  
  130.             break;  
  131.         }  
  132.         return super.onTouchEvent(ev);  
  133.     }  
  134.   
  135.     @Override  
  136.     public void computeScroll() {  
  137.   
  138.         // 松手之后调用  
  139.         if (scroller.computeScrollOffset()) {  
  140.   
  141.             if (mScrollBack == SCROLLBACK_HEADER) {  
  142.                 headerView.setVisiableHeight(scroller.getCurrY());  
  143.             } else {  
  144.                 footerView.setBottomMargin(scroller.getCurrY());  
  145.             }  
  146.             postInvalidate();  
  147.         }  
  148.         super.computeScroll();  
  149.   
  150.     }  
  151.   
  152.     public void setPullRefreshEnable(boolean enable) {  
  153.         enableRefresh = enable;  
  154.   
  155.         if (!enableRefresh) {  
  156.             headerView.hide();  
  157.         } else {  
  158.             headerView.show();  
  159.         }  
  160.     }  
  161.   
  162.     public void setPullLoadEnable(boolean enable) {  
  163.         enableLoadMore = enable;  
  164.         if (!enableLoadMore) {  
  165.             footerView.hide();  
  166.             footerView.setOnClickListener(null);  
  167.         } else {  
  168.             isLoadingMore = false;  
  169.             footerView.show();  
  170.             footerView.setState(XListViewFooter.STATE_NORMAL);  
  171.             footerView.setOnClickListener(new OnClickListener() {  
  172.                 @Override  
  173.                 public void onClick(View v) {  
  174.                     startLoadMore();  
  175.                 }  
  176.             });  
  177.         }  
  178.     }  
  179.   
  180.     public void stopRefresh() {  
  181.         if (isRefreashing == true) {  
  182.             isRefreashing = false;  
  183.             resetHeaderHeight();  
  184.         }  
  185.     }  
  186.   
  187.     public void stopLoadMore() {  
  188.         if (isLoadingMore == true) {  
  189.             isLoadingMore = false;  
  190.             footerView.setState(XListViewFooter.STATE_NORMAL);  
  191.         }  
  192.     }  
  193.   
  194.     private void updateHeaderHeight(float delta) {  
  195.         headerView.setVisiableHeight((int) delta  
  196.                 + headerView.getVisiableHeight());  
  197.         // 未处于刷新状态,更新箭头  
  198.         if (enableRefresh && !isRefreashing) {  
  199.             if (headerView.getVisiableHeight() > headerHeight) {  
  200.                 headerView.setState(XListViewHeader.STATE_READY);  
  201.             } else {  
  202.                 headerView.setState(XListViewHeader.STATE_NORMAL);  
  203.             }  
  204.         }  
  205.   
  206.     }  
  207.   
  208.     private void resetHeaderHeight() {  
  209.         // 当前的可见高度  
  210.         int height = headerView.getVisiableHeight();  
  211.         // 如果正在刷新并且高度没有完全展示  
  212.         if ((isRefreashing && height <= headerHeight) || (height == 0)) {  
  213.             return;  
  214.         }  
  215.         // 默认会回滚到header的位置  
  216.         int finalHeight = 0;  
  217.         // 如果是正在刷新状态,则回滚到header的高度  
  218.         if (isRefreashing && height > headerHeight) {  
  219.             finalHeight = headerHeight;  
  220.         }  
  221.         mScrollBack = SCROLLBACK_HEADER;  
  222.         // 回滚到指定位置  
  223.         scroller.startScroll(0, height, 0, finalHeight - height,  
  224.                 SCROLL_DURATION);  
  225.         // 触发computeScroll  
  226.         invalidate();  
  227.     }  
  228.   
  229.     private void updateFooterHeight(float delta) {  
  230.         int height = footerView.getBottomMargin() + (int) delta;  
  231.         if (enableLoadMore && !isLoadingMore) {  
  232.             if (height > PULL_LOAD_MORE_DELTA) {  
  233.                 footerView.setState(XListViewFooter.STATE_READY);  
  234.             } else {  
  235.                 footerView.setState(XListViewFooter.STATE_NORMAL);  
  236.             }  
  237.         }  
  238.         footerView.setBottomMargin(height);  
  239.   
  240.     }  
  241.   
  242.     private void resetFooterHeight() {  
  243.         int bottomMargin = footerView.getBottomMargin();  
  244.         if (bottomMargin > 0) {  
  245.             mScrollBack = SCROLLBACK_FOOTER;  
  246.             scroller.startScroll(0, bottomMargin, 0, -bottomMargin,  
  247.                     SCROLL_DURATION);  
  248.             invalidate();  
  249.         }  
  250.     }  
  251.   
  252.     private void startLoadMore() {  
  253.         isLoadingMore = true;  
  254.         footerView.setState(XListViewFooter.STATE_LOADING);  
  255.         if (mListViewListener != null) {  
  256.             mListViewListener.onLoadMore();  
  257.         }  
  258.     }  
  259.   
  260.     public void setXListViewListener(IXListViewListener l) {  
  261.         mListViewListener = l;  
  262.     }  
  263.   
  264.     public interface IXListViewListener {  
  265.   
  266.         public void onRefresh();  
  267.   
  268.         public void onLoadMore();  
  269.     }  
  270. }  

    在三个构造函数中,都调用initView进行了header和footer的初始化,并且定义了一个Scroller,并传入了一个减速的插值器,为了模仿回弹效果。在initView方法里面,因为header可能还没初始化完毕,所以通过GlobalLayoutlistener来获取了header的高度,然后addHeaderView添加到了listview上面。

    通过重写setAdapter方法,保证Footer最后天假,并且只添加一次。

    最重要的,要属onTouchEvent了。在方法开始之前,通过getAdapter().getCount()获取到了item的总数,便于计算位置。这个操作在源代码中是通过scrollerListener完成的,因为ScrollerListener在这里没大有用,所以我直接去掉了,然后把位置改到了这里。如果在setAdapter里面获取的话,只能获取到没有header和footer的item数量。

    在ACTION_DOWN里面,进行了lastY的初始化,lastY是为了判断移动方向的,因为在ACTION_MOVE里面,通过ev.getRawY()-lastY可以计算出手指的移动趋势,如果>0,那么就是向下滑动,反之向上。getRowY()是获取元Y坐标,意思就是和Window和View坐标没有关系的坐标,代表在屏幕上的绝对位置。然后在下面的代码里面,如果第一项可见并且header的可见高度>0或者是向下滑动,就说明用户在向下拉动或者是向上拉动header,也就是指示箭头显示的时候的状态,这时候调用了updateHeaderHeight,来更新header的高度,实现header可以跟随手指动作上下移动。这里有个OFFSET_RADIO,这个值是一个移动比例,就是说,你手指在Y方向上移动400px,如果比例是2,那么屏幕上的控件移动就是400px/2=200px,可以通过这个值来控制用户的滑动体验。下面的关于footer的判断与此类似,不再赘述。

   当用户移开手指之后,ACTION_UP方法就会被调用。在这里面,只对可见位置是0和item总数-1的位置进行了处理,其实正好对应header和footer。如果位置是0,并且可以刷新,然后当前的header可见高度>原始高度的话,就说明用户确实是要进行刷新操作,所以通过setState改变header的状态,如果有监听器的话,就调用onRefresh方法,然后调用resetHeaderHeight初始化header的状态,因为footer的操作如出一辙,所以不再赘述。但是在footer中有一个PULL_LOAD_MORE_DELTA,这个值是加载更多触发条件的临界值,只有footer的间隔超过这个值之后,才能够触发加载更多的功能,因此我们可以修改这个值来改变用户体验。

    说到现在,大家应该明白基本的原理了,其实XListView就是通过对用户手势的方向和距离的判断,来动态的改变Header和Footer实现的功能,所以如果我们也有类似的需求,就可以参照这种思路进行自定义。

    下面再说几个比较重要的方法。

    前面我们说道,在ACTION_MOVE里面,会不断的调用下面的updateXXXX方法,来动态的改变header和fooer的状态,

[java]  view plain copy 在CODE上查看代码片 派生到我的代码片
  1. private void updateHeaderHeight(float delta) {  
  2.         headerView.setVisiableHeight((int) delta  
  3.                 + headerView.getVisiableHeight());  
  4.         // 未处于刷新状态,更新箭头  
  5.         if (enableRefresh && !isRefreashing) {  
  6.             if (headerView.getVisiableHeight() > headerHeight) {  
  7.                 headerView.setState(XListViewHeader.STATE_READY);  
  8.             } else {  
  9.                 headerView.setState(XListViewHeader.STATE_NORMAL);  
  10.             }  
  11.         }  
  12.   
  13.     }  
  14.   
  15. private void updateFooterHeight(float delta) {  
  16.         int height = footerView.getBottomMargin() + (int) delta;  
  17.         if (enableLoadMore && !isLoadingMore) {  
  18.             if (height > PULL_LOAD_MORE_DELTA) {  
  19.                 footerView.setState(XListViewFooter.STATE_READY);  
  20.             } else {  
  21.                 footerView.setState(XListViewFooter.STATE_NORMAL);  
  22.             }  
  23.         }  
  24.         footerView.setBottomMargin(height);  
  25.   
  26.     }  
    在移开手指之后,会调用下面的resetXXX来初始化header和footer的状态

[java]  view plain copy 在CODE上查看代码片 派生到我的代码片
  1. private void resetHeaderHeight() {  
  2.         // 当前的可见高度  
  3.         int height = headerView.getVisiableHeight();  
  4.         // 如果正在刷新并且高度没有完全展示  
  5.         if ((isRefreashing && height <= headerHeight) || (height == 0)) {  
  6.             return;  
  7.         }  
  8.         // 默认会回滚到header的位置  
  9.         int finalHeight = 0;  
  10.         // 如果是正在刷新状态,则回滚到header的高度  
  11.         if (isRefreashing && height > headerHeight) {  
  12.             finalHeight = headerHeight;  
  13.         }  
  14.         mScrollBack = SCROLLBACK_HEADER;  
  15.         // 回滚到指定位置  
  16.         scroller.startScroll(0, height, 0, finalHeight - height,  
  17.                 SCROLL_DURATION);  
  18.         // 触发computeScroll  
  19.         invalidate();  
  20.     }  
  21.   
  22. private void resetFooterHeight() {  
  23.         int bottomMargin = footerView.getBottomMargin();  
  24.         if (bottomMargin > 0) {  
  25.             mScrollBack = SCROLLBACK_FOOTER;  
  26.             scroller.startScroll(0, bottomMargin, 0, -bottomMargin,  
  27.                     SCROLL_DURATION);  
  28.             invalidate();  
  29.         }  
  30.     }  
    我们可以看到,滚动操作不是通过直接的设置高度来实现的,而是通过Scroller.startScroll()来实现的,通过调用此方法,computeScroll()就会被调用,然后在这个里面,根据mScrollBack区分是哪一个滚动,然后再通过设置高度和间隔,就可以完成收缩的效果了。
    至此,整个XListView的实现原理就完全的搞明白了,以后如果做滚动类的自定义控件,应该也有思路了。
Android高效列表控件XListView实战使用指南 XListView作为对Android原生ListView的增强封装,通过集成下拉刷新、上拉加载更多等高频交互功能,显著提升了列表类界面的开发效率。其核心设计遵循“扩展而不重写”的原则,保留ListView原有API兼容性的同时,引入可插拔式的刷新组件(HeaderView)与加载组件(FooterView),实现功能解耦。事件拦截机制基于进行精准滑动判断,确保在复杂布局中仍能稳定响应用户操作。XListView对外暴露方法,允许注册一个监听器来接收刷新事件。 阅读详情

相关推荐

Android高效实现ListView上拉下拉刷新与加载更多

本文还有配套的精品资源,点击获取 简介:在Android开发中,ListView组件常用于展示数据列表。为了改善用户体验,开发者需要为ListView添加上拉下拉刷新和加载更多数据的功能。本课程将探讨实现这些功能的不同方法,包括使用第三方库如SwipeRefreshLayout或XListView,以及自定义实现滚动监听逻辑。课程将涉及自定义行为、性能优化和不同设备及And...

weixin_42298778的博客 1511

ListView之二 XListView

本次分析的是github上一个用于下拉刷新上拉加载的控件XListView,这个开源的ListView相信大家在开发的过程中会经常用到。 XListView控件的源码地址是https://github.com/Maxwin-z/XListView-Android 参考了其源码与网上的各种分析总结而成。 1.   XListView功能 XListView中我们经常用得就是下拉刷新和

带你装逼带你飞的专栏 694

XListView的使用

支持下拉刷新,上拉刷新,添加头视图,尾视图,

XListView的介绍和使用

在开发的过程中,wo'm

forwardyzk的专栏 1万+

在Kintex-7 FPGA上实现皮秒级时间测量:手把手教你搭建CARRY4进位链TDC(附源码与约束)

本文详细介绍了在Kintex-7 FPGA上利用CARRY4进位链实现皮秒级时间测量(TDC)的完整方案。从CARRY4的物理特性分析到硬件架构设计,再到校准方法与精度提升技巧,手把手教你构建测量精度达53ps的TDC系统,并附源码与约束文件,助力激光雷达、高能物理等精密计时应用开发。

weixin_42550052的博客 448

Fusion 360嵌入式外壳建模实战:从草图到可装配结构

参数化三维建模是现代电子结构设计的核心能力,其本质是通过几何约束与特征驱动实现设计意图的精确表达和快速迭代。Fusion 360作为集成CAD/CAM/CAE的云端平台,以时间轴驱动的参数化工作流、基于基准面的草图约束机制和面向制造的特征操作逻辑,显著提升了嵌入式硬件的结构验证效率。该技术价值在于支持工程师在无专业机械背景前提下,独立完成PCB适配、散热布局、接口开孔与装配干涉检查等关键任务,广泛应用于3D打印原型、CNC加工外壳及IoT终端结构开发。本文以ESP32冰箱监控终端为实例,详解从基准定义、草图

weixin_35671843的博客 590

XListView

XListView XListView是github上的开源第三方控件,内部封装好的一个类XListView 其内部包含XListViewFooter XListViewHeader 两个自定义类(控件)如此实现上拉加载更多、下拉刷新,此控件内部引用自己的布局 作为加载的界面,此控件类似于PulltoRefreshView 只不过比PulltoRefreshView上拉时没有显示加载更多的视

DistanceSY的博客 412

别再只看读写速度了!聊聊手机闪存eMMC、UFS3.1和SSD背后的功耗与体积玄学

本文深入探讨手机闪存技术eMMC、UFS3.1和SSD在功耗与体积设计上的关键差异。通过对比接口技术、封装工艺和温度管理,揭示移动存储如何在有限空间内平衡性能与能效,为消费者提供选购参考。重点关注UFS3.1的差分信号设计和全双工架构带来的显著优势。

weixin_27061475的博客 452

探索XListView:一款强大的Android列表视图组件

探索XListView:一款强大的Android列表视图组件 引言 在Android应用开发中,列表视图(ListView)是不可或缺的一部分,它用于展示大量可滚动的数据。今天,我们将会深入探讨一个名为的开源项目,这是一个增强型的ListView,提供了丰富的功能和高度的自定义性,旨在提升用户的交互体验。 项目简介 是由limxing开发的一个Android库,它在标准ListView的基础上增加...

gitblog_00099的博客 789

《AI大模型评测与智能测试》

第三篇“实践蓝图”(第6-7章)着眼于测试领域AI应用,重点介绍了RAG、AI Agent开发及构建端到端智能测试系统的方法。本书聚焦AI时代的“质量”核心,以“AI赋能测试”(AI For Testing)与“AI模型评测”(Testing For AI)为双轮驱动。内容不仅深入剖析如何科学度量AI大模型的内在质量,还系统讲解如何利用AI赋能传统软件测试,帮助读者贯通从模型原理、评测到应用开发的全链路知识。

酌沧 1142

深入浅出DDR系列(二)--DDR工作原理

学习完了DDR的基本组成和硬件结构原理之后,从整体上了解了DDR的特性和静态图,包括原理、管脚图等。那么本章就要从动态的角度来分析时序结构,包括read/write的整个过程到数据返回发生了什么,其中包括以下内容 DDR是如何完成Read、Write 等的操作 DDR的基本命令 DDR的时序参数 DDR的性能分析 1. DRAM基本组成 对于DRAM,其主要由行和列组成,每一个bit中都是由类似右下图的类晶体管的结构组成,对于sdram的数据,可以通过控制column和row就可以访问sd

奇小葩 3万+

Android 下拉刷新控件XListView原理解析

在Android app开发过程中,使用下拉刷新控件的机会是非常多的,比如列表页或是首页,一般都是要下拉刷新的。在Github中下拉刷新控件有很多,但是我现在介绍的是已经停更很久的XListView,因为我觉得这个库写的简介明了,功能稳定,bug少。非常适合自己学习下拉刷新的原理。面试的时候也通常会问到某些控件的原理,所以,了解一下还是很有必要的。XListView在github中的仓库:http...

u011803341的专栏 462

【HAL库-STM】智能温控按键小风扇【胎教级教学】

硬件设备:DHT11,0.96寸oled屏,二/三/四线风扇(不挑),面包板 、f10的精英板等等。当人在的风扇面前时可以自行控制风扇,不在面前时又想开风扇就让他温控自行开启,(在这里有一个想法就是加上蓝牙和wifi控制的功能能够远程遥控,这个如果做的再全一点就要自主研发是一个多功能app,这里没有做。针对这个dht11,我一开始是卡在了这个check函数里面,一直是我给完dht11低电平和高电平后,dht11不回复我低电平和高电平,这个不是我的问题,当时代码是没毛病的,这个点一直卡着我。吐血五天实现小成品

weixin_52451652的博客 1741

ListView扩展实现XListView、ZListView

标题标题XListView 【Android界面实现】XListView实现原理讲解及分析 基于Android官方AsyncListUtil优化改进RecyclerView分页加载机制(一)

ThePromonkeyOf_HeLuo的博客 450

如何使用Python爬取并保存网页上的所有附件

爬虫(Crawler)是一种自动化程序,它通过模拟人工访问网页并下载网页上的数据或文件,常用于数据采集和分析。爬虫通常以“爬取”的形式自动浏览和提取网页中的各种信息。本文详细介绍了如何使用Python爬取并保存网页中的附件文件,涵盖了网页解析、文件下载、多线程与异步优化等内容。希望通过本文的讲解,能够帮助你理解并掌握附件爬取的技巧。在实际应用中,爬虫开发不仅需要解决技术问题,还要遵守网络道德和法律法规,避免不当使用爬虫造成负面影响。

2201_76125261的博客 968

Open3D 使用RANSAC分割平面【2026最新版】

使用RANSAC从点云中分割平面,用segement_plane函数。博客长期更新,本文最新更新时间为:2026年2月28日。

点云侠的博客 37万+

Android下拉刷新

早期下拉刷新主要是用XListView,这个类是继承ListView的,使用起来有局限性,并且在git上作者已经不维护了,之后有人自己写了一个PullToRefreshLayout,不用再继承自ListView,而是直接嵌套在所有继承于AbsListView的外面,如下:<com.fq.pulltorefresh.PullToRefreshLayout xmlns:android="http://s

swjtufq的博客 293

LinePhone3.2.7(android端最新版本) sip客户端

SIP客户端For android:LinePhone3.2.7(最新版本),官网地址:http://www.linphone.org/

XListView介绍与使用(附加核心代码)

1:说到XlistView其实它和ListView一样都是用于展示数据使用的控件,XListView继承ListView它与listview不同的是,它封装可以下拉刷新,上拉加载更多的方法,我们只需要重写它的方法即可实现刷新与加载更多的操作。 2:使用XListView需要导入lib包,然后添加到当前module中就可以使用。 核心代码如下: package co

gsc_19970804的博客 375

IEM模型,地表粗糙度模拟

利用微波反演土壤水分时候,利用IEM模型可以实现对随机地表的模拟

贝叶斯统计 [韦来生编]

贝叶斯(Bayes)统计是近几十年来迅速发展起来的数理统计的一个重要分支。贝叶斯方法与经典统计方法的主要不同之处是进行统计推断时除了利用样本信息外,还要利用参数的先验信息,因此可以提高统计推断或统计决策的效果。它在经济、金融、生物、医学、自然科学和社会科学等许多领域具有广泛的应用。贝叶斯方法的研究已渗透到了统计学的几乎所有领域。作者在给中国科学技术大学概率论与数理统计专业本科生开设的“数理统计”课程中有一章专门讲授“贝叶斯方法和统计决策理论”,近十年来也给中国科学技术大学概率统计专业研究生开设过几次“贝叶斯分析”课。本书是在对过去讲稿的内容作了适当的增减和调整的基础上完成的。  本书共分六章。第1章是绪论,介绍了贝叶斯统计的若干基本概念,同时对必要的数理统计的基础知识有重点地作了回顾。第二章介绍了确定先验分布的若干可供选择的方法。第三章介绍了几类常见统计模型参数的后验分布的主要结果和计算方法。第四章和第五章分别介绍了“贝叶斯统计推断”和“贝叶斯统计决策”的内容。第六章介绍了贝叶斯统计计算的若干方法,包括蒙特卡洛方法和马尔可夫链蒙特卡洛(MCMC)方法以及相关统计软件的简介。本书的每一章附右大量的例题和习题,书末附有常用的几个表格和部分习题解答供读者查用。  大约可在54学时内讲授本书的第1章至第六章的主要内容,教师可根据学时需要选讲本书第1章至第六章的部分内容。书中标“*”号的章节可略去不讲,留给读者作为阅读材料。第六章例题中的R代码和部分例题的数据文件等可从作者个人主页下载。  作者在编写本书的过程中参考了J.O.Berger教授的《统计决策论及贝叶斯分析》和茆诗松教授编写的《贝叶斯统计》等书中的一些内容,以及《贝叶斯分析》一书中由张伟平博士编写的有关贝叶斯统计计算的一些内容。在此表示衷心感谢。  本书编写准备过程中,中国科学技术大学统计与金融系的几个研究生帮助完成了本书部分中文的录入和排版,作者对他们的辛勤工作表示真诚的感谢。高等教育出版社为本书的出版给予了大力支持。在此一并致谢。

上一篇: 整合了刷新、加载更多、滑动删除功能的XListview
下一篇: Android第三方框架之xListView的使用方法
codepython
博客等级 码龄20年 125粉丝 6原创
评论
成就一亿技术人!
拼手气红包6.0元
还能输入1000个字符
 
 条评论被折叠 查看
添加红包

请填写红包祝福语或标题

个

红包个数最小为10个

元

红包金额最低5元

当前余额3.43元 前往充值 >
需支付:10.00元
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付元
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值