• android ListView的上部下拉刷新下部点击加载更多具体实现及拓展


    转自:http://blog.csdn.net/jj120522/article/details/8229423

    这次就不上图了,例子太多太多了,想必大家都见过.这个功能的实现,简直是开发者必备的.

    我也不过多介绍了,网上详细介绍的博客太多太多了,若想深入了解,请参考网上其他博文.

    在这里,我只是按照自己的理解,模拟实现了一个,顺便代码贡献出来.

    我对之详细标明的注释,想必如果不懂的同学们,看注释也应该明白,前提是,你要耐心看,因为代码有点多,但是我整理过了,还算清晰.

    详细代码:

    [java] view plaincopy
     
    1. package com.jj.drag;  
    2.   
    3. import android.content.Context;  
    4. import android.util.AttributeSet;  
    5. import android.util.Log;  
    6. import android.view.LayoutInflater;  
    7. import android.view.MotionEvent;  
    8. import android.view.View;  
    9. import android.view.View.OnClickListener;  
    10. import android.view.ViewGroup;  
    11. import android.view.animation.Animation;  
    12. import android.view.animation.LinearInterpolator;  
    13. import android.view.animation.RotateAnimation;  
    14. import android.widget.AbsListView;  
    15. import android.widget.AbsListView.OnScrollListener;  
    16. import android.widget.ImageView;  
    17. import android.widget.LinearLayout;  
    18. import android.widget.ListView;  
    19. import android.widget.ProgressBar;  
    20. import android.widget.RelativeLayout;  
    21. import android.widget.TextView;  
    22.   
    23. /*** 
    24.  * 自定义拖拉ListView 
    25.  *  
    26.  * @author zhangjia 
    27.  *  
    28.  */  
    29. public class DragListView extends ListView implements OnScrollListener,  
    30.         OnClickListener {  
    31.     // 拖拉ListView枚举所有状态  
    32.     private enum DListViewState {  
    33.         LV_NORMAL, // 普通状态  
    34.         LV_PULL_REFRESH, // 下拉状态(为超过mHeadViewHeight)  
    35.         LV_RELEASE_REFRESH, // 松开可刷新状态(超过mHeadViewHeight)  
    36.         LV_LOADING;// 加载状态  
    37.     }  
    38.   
    39.     // 点击加载更多枚举所有状态  
    40.     private enum DListViewLoadingMore {  
    41.         LV_NORMAL, // 普通状态  
    42.         LV_LOADING, // 加载状态  
    43.         LV_OVER; // 结束状态  
    44.     }  
    45.   
    46.     private View mHeadView;// 头部headView  
    47.     private TextView mRefreshTextview; // 刷新msg(mHeadView)  
    48.     private TextView mLastUpdateTextView;// 更新事件(mHeadView)  
    49.     private ImageView mArrowImageView;// 下拉图标(mHeadView)  
    50.     private ProgressBar mHeadProgressBar;// 刷新进度体(mHeadView)  
    51.   
    52.     private int mHeadViewWidth; // headView的宽(mHeadView)  
    53.     private int mHeadViewHeight;// headView的高(mHeadView)  
    54.   
    55.     private View mFootView;// 尾部mFootView  
    56.     private View mLoadMoreView;// mFootView 的view(mFootView)  
    57.     private TextView mLoadMoreTextView;// 加载更多.(mFootView)  
    58.     private View mLoadingView;// 加载中...View(mFootView)  
    59.   
    60.     private Animation animation, reverseAnimation;// 旋转动画,旋转动画之后旋转动画.  
    61.   
    62.     private int mFirstItemIndex = -1;// 当前视图能看到的第一个项的索引  
    63.   
    64.     // 用于保证startY的值在一个完整的touch事件中只被记录一次  
    65.     private boolean mIsRecord = false;  
    66.   
    67.     private int mStartY, mMoveY;// 按下是的y坐标,move时的y坐标  
    68.   
    69.     private DListViewState mlistViewState = DListViewState.LV_NORMAL;// 拖拉状态.(自定义枚举)  
    70.   
    71.     private DListViewLoadingMore loadingMoreState = DListViewLoadingMore.LV_NORMAL;// 加载更多默认状态.  
    72.   
    73.     private final static int RATIO = 2;// 手势下拉距离比.  
    74.   
    75.     private boolean mBack = false;// headView是否返回.  
    76.   
    77.     private OnRefreshLoadingMoreListener onRefreshLoadingMoreListener;// 下拉刷新接口(自定义)  
    78.   
    79.     private boolean isScroller = true;// 是否屏蔽ListView滑动。  
    80.   
    81.     public DragListView(Context context) {  
    82.         super(context, null);  
    83.         initDragListView(context);  
    84.     }  
    85.   
    86.     public DragListView(Context context, AttributeSet attrs) {  
    87.         super(context, attrs);  
    88.         initDragListView(context);  
    89.     }  
    90.   
    91.     // 注入下拉刷新接口  
    92.     public void setOnRefreshListener(  
    93.             OnRefreshLoadingMoreListener onRefreshLoadingMoreListener) {  
    94.         this.onRefreshLoadingMoreListener = onRefreshLoadingMoreListener;  
    95.     }  
    96.   
    97.     /*** 
    98.      * 初始化ListView 
    99.      */  
    100.     public void initDragListView(Context context) {  
    101.   
    102.         String time = "1994.12.05";// 更新时间  
    103.   
    104.         initHeadView(context, time);// 初始化该head.  
    105.   
    106.         initLoadMoreView(context);// 初始化footer  
    107.   
    108.         setOnScrollListener(this);// ListView滚动监听  
    109.     }  
    110.   
    111.     /*** 
    112.      * 初始话头部HeadView 
    113.      *  
    114.      * @param context 
    115.      *            上下文 
    116.      * @param time 
    117.      *            上次更新时间 
    118.      */  
    119.     public void initHeadView(Context context, String time) {  
    120.         mHeadView = LayoutInflater.from(context).inflate(R.layout.head, null);  
    121.         mArrowImageView = (ImageView) mHeadView  
    122.                 .findViewById(R.id.head_arrowImageView);  
    123.         mArrowImageView.setMinimumWidth(60);  
    124.   
    125.         mHeadProgressBar = (ProgressBar) mHeadView  
    126.                 .findViewById(R.id.head_progressBar);  
    127.   
    128.         mRefreshTextview = (TextView) mHeadView  
    129.                 .findViewById(R.id.head_tipsTextView);  
    130.   
    131.         mLastUpdateTextView = (TextView) mHeadView  
    132.                 .findViewById(R.id.head_lastUpdatedTextView);  
    133.         // 显示更新事件  
    134.         mLastUpdateTextView.setText("最近更新:" + time);  
    135.   
    136.         measureView(mHeadView);  
    137.         // 获取宽和高  
    138.         mHeadViewWidth = mHeadView.getMeasuredWidth();  
    139.         mHeadViewHeight = mHeadView.getMeasuredHeight();  
    140.   
    141.         addHeaderView(mHeadView, null, false);// 将初始好的ListView add进拖拽ListView  
    142.         // 在这里我们要将此headView设置到顶部不显示位置.  
    143.         mHeadView.setPadding(0, -1 * mHeadViewHeight, 0, 0);  
    144.   
    145.         initAnimation();// 初始化动画  
    146.     }  
    147.   
    148.     /*** 
    149.      * 初始化底部加载更多控件 
    150.      */  
    151.     private void initLoadMoreView(Context context) {  
    152.         mFootView = LayoutInflater.from(context).inflate(R.layout.footer, null);  
    153.   
    154.         mLoadMoreView = mFootView.findViewById(R.id.load_more_view);  
    155.   
    156.         mLoadMoreTextView = (TextView) mFootView  
    157.                 .findViewById(R.id.load_more_tv);  
    158.   
    159.         mLoadingView = (LinearLayout) mFootView  
    160.                 .findViewById(R.id.loading_layout);  
    161.   
    162.         mLoadMoreView.setOnClickListener(this);  
    163.   
    164.         addFooterView(mFootView);  
    165.     }  
    166.   
    167.     /*** 
    168.      * 初始化动画 
    169.      */  
    170.     private void initAnimation() {  
    171.         // 旋转动画  
    172.         animation = new RotateAnimation(0, -180,  
    173.                 RotateAnimation.RELATIVE_TO_SELF, 0.5f,  
    174.                 RotateAnimation.RELATIVE_TO_SELF, 0.5f);  
    175.         animation.setInterpolator(new LinearInterpolator());// 匀速  
    176.         animation.setDuration(250);  
    177.         animation.setFillAfter(true);// 停留在最后状态.  
    178.         // 反向旋转动画  
    179.         reverseAnimation = new RotateAnimation(-180, 0,  
    180.                 RotateAnimation.RELATIVE_TO_SELF, 0.5f,  
    181.                 RotateAnimation.RELATIVE_TO_SELF, 0.5f);  
    182.         reverseAnimation.setInterpolator(new LinearInterpolator());  
    183.         reverseAnimation.setDuration(250);  
    184.         reverseAnimation.setFillAfter(true);  
    185.     }  
    186.   
    187.     /*** 
    188.      * 作用:测量 headView的宽和高. 
    189.      *  
    190.      * @param child 
    191.      */  
    192.     private void measureView(View child) {  
    193.         ViewGroup.LayoutParams p = child.getLayoutParams();  
    194.         if (p == null) {  
    195.             p = new ViewGroup.LayoutParams(ViewGroup.LayoutParams.FILL_PARENT,  
    196.                     ViewGroup.LayoutParams.WRAP_CONTENT);  
    197.         }  
    198.         int childWidthSpec = ViewGroup.getChildMeasureSpec(0, 0 + 0, p.width);  
    199.         int lpHeight = p.height;  
    200.         int childHeightSpec;  
    201.         if (lpHeight > 0) {  
    202.             childHeightSpec = MeasureSpec.makeMeasureSpec(lpHeight,  
    203.                     MeasureSpec.EXACTLY);  
    204.         } else {  
    205.             childHeightSpec = MeasureSpec.makeMeasureSpec(0,  
    206.                     MeasureSpec.UNSPECIFIED);  
    207.         }  
    208.         child.measure(childWidthSpec, childHeightSpec);  
    209.     }  
    210.   
    211.     /*** 
    212.      * touch 事件监听 
    213.      */  
    214.     @Override  
    215.     public boolean onTouchEvent(MotionEvent ev) {  
    216.         switch (ev.getAction()) {  
    217.         // 按下  
    218.         case MotionEvent.ACTION_DOWN:  
    219.             doActionDown(ev);  
    220.             break;  
    221.         // 移动  
    222.         case MotionEvent.ACTION_MOVE:  
    223.             doActionMove(ev);  
    224.             break;  
    225.         // 抬起  
    226.         case MotionEvent.ACTION_UP:  
    227.             doActionUp(ev);  
    228.             break;  
    229.         default:  
    230.             break;  
    231.         }  
    232.         /*** 
    233.          * 如果是ListView本身的拉动,那么返回true,这样ListView不可以拖动. 
    234.          * 如果不是ListView的拉动,那么调用父类方法,这样就可以上拉执行. 
    235.          */  
    236.         if (isScroller) {  
    237.             return super.onTouchEvent(ev);  
    238.         } else {  
    239.             return true;  
    240.         }  
    241.   
    242.     }  
    243.   
    244.     /*** 
    245.      * 摁下操作 
    246.      *  
    247.      * 作用:获取摁下是的y坐标 
    248.      *  
    249.      * @param event 
    250.      */  
    251.     void doActionDown(MotionEvent event) {  
    252.         if (mIsRecord == false && mFirstItemIndex == 0) {  
    253.             mStartY = (int) event.getY();  
    254.             mIsRecord = true;  
    255.         }  
    256.     }  
    257.   
    258.     /*** 
    259.      * 拖拽移动操作 
    260.      *  
    261.      * @param event 
    262.      */  
    263.     void doActionMove(MotionEvent event) {  
    264.         mMoveY = (int) event.getY();// 获取实时滑动y坐标  
    265.         // 检测是否是一次touch事件.  
    266.         if (mIsRecord == false && mFirstItemIndex == 0) {  
    267.             mStartY = (int) event.getY();  
    268.             mIsRecord = true;  
    269.         }  
    270.         /*** 
    271.          * 如果touch关闭或者正处于Loading状态的话 return. 
    272.          */  
    273.         if (mIsRecord == false || mlistViewState == DListViewState.LV_LOADING) {  
    274.             return;  
    275.         }  
    276.         // 向下啦headview移动距离为y移动的一半.(比较友好)  
    277.         int offset = (mMoveY - mStartY) / RATIO;  
    278.   
    279.         switch (mlistViewState) {  
    280.         // 普通状态  
    281.         case LV_NORMAL: {  
    282.             // 如果<0,则意味着上滑动.  
    283.             if (offset > 0) {  
    284.                 // 设置headView的padding属性.  
    285.                 mHeadView.setPadding(0, offset - mHeadViewHeight, 0, 0);  
    286.                 switchViewState(DListViewState.LV_PULL_REFRESH);// 下拉状态  
    287.             }  
    288.   
    289.         }  
    290.             break;  
    291.         // 下拉状态  
    292.         case LV_PULL_REFRESH: {  
    293.             setSelection(0);// 时时保持在顶部.  
    294.             // 设置headView的padding属性.  
    295.             mHeadView.setPadding(0, offset - mHeadViewHeight, 0, 0);  
    296.             if (offset < 0) {  
    297.                 /*** 
    298.                  * 要明白为什么isScroller = false; 
    299.                  */  
    300.                 isScroller = false;  
    301.                 switchViewState(DListViewState.LV_NORMAL);// 普通状态  
    302.                 Log.e("jj", "isScroller=" + isScroller);  
    303.             } else if (offset > mHeadViewHeight) {// 如果下拉的offset超过headView的高度则要执行刷新.  
    304.                 switchViewState(DListViewState.LV_RELEASE_REFRESH);// 更新为可刷新的下拉状态.  
    305.             }  
    306.         }  
    307.             break;  
    308.         // 可刷新状态  
    309.         case LV_RELEASE_REFRESH: {  
    310.             setSelection(0);时时保持在顶部  
    311.             // 设置headView的padding属性.  
    312.             mHeadView.setPadding(0, offset - mHeadViewHeight, 0, 0);  
    313.             // 下拉offset>0,但是没有超过headView的高度.那么要goback 原装.  
    314.             if (offset >= 0 && offset <= mHeadViewHeight) {  
    315.                 mBack = true;  
    316.                 switchViewState(DListViewState.LV_PULL_REFRESH);  
    317.             } else if (offset < 0) {  
    318.                 switchViewState(DListViewState.LV_NORMAL);  
    319.             } else {  
    320.   
    321.             }  
    322.         }  
    323.             break;  
    324.         default:  
    325.             return;  
    326.         }  
    327.         ;  
    328.     }  
    329.   
    330.     /*** 
    331.      * 手势抬起操作 
    332.      *  
    333.      * @param event 
    334.      */  
    335.     public void doActionUp(MotionEvent event) {  
    336.         mIsRecord = false;// 此时的touch事件完毕,要关闭。  
    337.         isScroller = true;// ListView可以Scrooler滑动.  
    338.         mBack = false;  
    339.         // 如果下拉状态处于loading状态.  
    340.         if (mlistViewState == DListViewState.LV_LOADING) {  
    341.             return;  
    342.         }  
    343.         // 处理相应状态.  
    344.         switch (mlistViewState) {  
    345.         // 普通状态  
    346.         case LV_NORMAL:  
    347.   
    348.             break;  
    349.         // 下拉状态  
    350.         case LV_PULL_REFRESH:  
    351.             mHeadView.setPadding(0, -1 * mHeadViewHeight, 0, 0);  
    352.             switchViewState(mlistViewState.LV_NORMAL);  
    353.             break;  
    354.         // 刷新状态  
    355.         case LV_RELEASE_REFRESH:  
    356.             mHeadView.setPadding(0, 0, 0, 0);  
    357.             switchViewState(mlistViewState.LV_LOADING);  
    358.             onRefresh();// 下拉刷新  
    359.             break;  
    360.         }  
    361.   
    362.     }  
    363.   
    364.     // 切换headview视图  
    365.     private void switchViewState(DListViewState state) {  
    366.   
    367.         switch (state) {  
    368.         // 普通状态  
    369.         case LV_NORMAL: {  
    370.             mArrowImageView.clearAnimation();// 清除动画  
    371.             mArrowImageView.setImageResource(R.drawable.arrow);  
    372.         }  
    373.             break;  
    374.         // 下拉状态  
    375.         case LV_PULL_REFRESH: {  
    376.             mHeadProgressBar.setVisibility(View.GONE);// 隐藏进度条  
    377.             mArrowImageView.setVisibility(View.VISIBLE);// 下拉图标  
    378.             mRefreshTextview.setText("下拉可以刷新");  
    379.             mArrowImageView.clearAnimation();// 清除动画  
    380.   
    381.             // 是有可刷新状态(LV_RELEASE_REFRESH)转为这个状态才执行,其实就是你下拉后在上拉会执行.  
    382.             if (mBack) {  
    383.                 mBack = false;  
    384.                 mArrowImageView.clearAnimation();// 清除动画  
    385.                 mArrowImageView.startAnimation(reverseAnimation);// 启动反转动画  
    386.             }  
    387.         }  
    388.             break;  
    389.         // 松开刷新状态  
    390.         case LV_RELEASE_REFRESH: {  
    391.             mHeadProgressBar.setVisibility(View.GONE);// 隐藏进度条  
    392.             mArrowImageView.setVisibility(View.VISIBLE);// 显示下拉图标  
    393.             mRefreshTextview.setText("松开获取更多");  
    394.             mArrowImageView.clearAnimation();// 清除动画  
    395.             mArrowImageView.startAnimation(animation);// 启动动画  
    396.         }  
    397.             break;  
    398.         // 加载状态  
    399.         case LV_LOADING: {  
    400.             Log.e("!!!!!!!!!!!", "convert to IListViewState.LVS_LOADING");  
    401.             mHeadProgressBar.setVisibility(View.VISIBLE);  
    402.             mArrowImageView.clearAnimation();  
    403.             mArrowImageView.setVisibility(View.GONE);  
    404.             mRefreshTextview.setText("载入中...");  
    405.         }  
    406.             break;  
    407.         default:  
    408.             return;  
    409.         }  
    410.         // 切记不要忘记时时更新状态。  
    411.         mlistViewState = state;  
    412.   
    413.     }  
    414.   
    415.     /*** 
    416.      * 下拉刷新 
    417.      */  
    418.     private void onRefresh() {  
    419.         if (onRefreshLoadingMoreListener != null) {  
    420.             onRefreshLoadingMoreListener.onRefresh();  
    421.         }  
    422.     }  
    423.   
    424.     /*** 
    425.      * 下拉刷新完毕 
    426.      */  
    427.     public void onRefreshComplete() {  
    428.         mHeadView.setPadding(0, -1 * mHeadViewHeight, 0, 0);// 回归.  
    429.         switchViewState(mlistViewState.LV_NORMAL);//  
    430.     }  
    431.   
    432.     /*** 
    433.      * 点击加载更多 
    434.      *  
    435.      * @param flag 
    436.      *            数据是否已全部加载完毕 
    437.      */  
    438.     public void onLoadMoreComplete(boolean flag) {  
    439.         if (flag) {  
    440.             updateLoadMoreViewState(DListViewLoadingMore.LV_OVER);  
    441.         } else {  
    442.             updateLoadMoreViewState(DListViewLoadingMore.LV_NORMAL);  
    443.         }  
    444.   
    445.     }  
    446.   
    447.     // 更新Footview视图  
    448.     private void updateLoadMoreViewState(DListViewLoadingMore state) {  
    449.         switch (state) {  
    450.         // 普通状态  
    451.         case LV_NORMAL:  
    452.             mLoadingView.setVisibility(View.GONE);  
    453.             mLoadMoreTextView.setVisibility(View.VISIBLE);  
    454.             mLoadMoreTextView.setText("查看更多");  
    455.             break;  
    456.         // 加载中状态  
    457.         case LV_LOADING:  
    458.             mLoadingView.setVisibility(View.VISIBLE);  
    459.             mLoadMoreTextView.setVisibility(View.GONE);  
    460.             break;  
    461.         // 加载完毕状态  
    462.         case LV_OVER:  
    463.             mLoadingView.setVisibility(View.GONE);  
    464.             mLoadMoreTextView.setVisibility(View.VISIBLE);  
    465.             mLoadMoreTextView.setText("加载完毕");  
    466.             break;  
    467.         default:  
    468.             break;  
    469.         }  
    470.         loadingMoreState = state;  
    471.     }  
    472.   
    473.     /*** 
    474.      * ListView 滑动监听 
    475.      */  
    476.     @Override  
    477.     public void onScrollStateChanged(AbsListView view, int scrollState) {  
    478.   
    479.     }  
    480.   
    481.     @Override  
    482.     public void onScroll(AbsListView view, int firstVisibleItem,  
    483.             int visibleItemCount, int totalItemCount) {  
    484.         mFirstItemIndex = firstVisibleItem;  
    485.     }  
    486.   
    487.     /*** 
    488.      * 底部点击事件 
    489.      */  
    490.     @Override  
    491.     public void onClick(View v) {  
    492.         // 防止重复点击  
    493.         if (onRefreshLoadingMoreListener != null  
    494.                 && loadingMoreState == DListViewLoadingMore.LV_NORMAL) {  
    495.             updateLoadMoreViewState(DListViewLoadingMore.LV_LOADING);  
    496.             onRefreshLoadingMoreListener.onLoadMore();// 对外提供方法加载更多.  
    497.         }  
    498.   
    499.     }  
    500.   
    501.     /*** 
    502.      * 自定义接口 
    503.      */  
    504.     public interface OnRefreshLoadingMoreListener {  
    505.         /*** 
    506.          * // 下拉刷新执行 
    507.          */  
    508.         void onRefresh();  
    509.   
    510.         /*** 
    511.          * 点击加载更多 
    512.          */  
    513.         void onLoadMore();  
    514.     }  
    515.   
    516. }  



    上面就是全部代码,其实重要的是明白理解,这样我们还可以进行拓展.

    具体应用:(只需要这样引用即可.)

    [java] view plaincopy
     
    1. <com.jj.drag.DragListView  
    2.     android:id="@+id/dlv_main"  
    3.     android:layout_width="fill_parent"  
    4.     android:layout_height="fill_parent"  
    5.     android:cacheColorHint="#00000000" />  

    在Activity中的调用,相比大家都清楚,开个异步或线程进行加载数据,这里我简单说一下异步使用,线程同理.

    代码如下:

    [java] view plaincopy
     
    1. /*** 
    2.      * 执行类 异步 
    3.      *  
    4.      * @author zhangjia 
    5.      *  
    6.      */  
    7.     class MyAsyncTask extends AsyncTask<Void, Void, Void> {  
    8.         private Context context;  
    9.         private int index;// 用于判断是下拉刷新还是点击加载更多  
    10.   
    11.         public MyAsyncTask(Context context, int index) {  
    12.             this.context = context;  
    13.             this.index = index;  
    14.         }  
    15.   
    16.         @Override  
    17.         protected Void doInBackground(Void... params) {  
    18.             try {  
    19.                 Thread.sleep(2000);  
    20.             } catch (InterruptedException e1) {  
    21.                 e1.printStackTrace();  
    22.             }  
    23.   
    24.             return null;  
    25.         }  
    26.   
    27.         @Override  
    28.         protected void onPreExecute() {  
    29.             super.onPreExecute();  
    30.         }  
    31.   
    32.         @Override  
    33.         protected void onPostExecute(Void result) {  
    34.             super.onPostExecute(result);  
    35.             if (index == DRAG_INDEX)  
    36.                 dlv_main.onRefreshComplete();  
    37.             else if (index == LOADMORE_INDEX)  
    38.                 dlv_main.onLoadMoreComplete(false);  
    39.         }  
    40.   
    41.     }  

    先声明一点,这个只是个示例,所以这部分代码写的不够友好,也请见谅.

    就说道这里,最后展示一下效果:

                    

    至于如果显示,如何adapter.notifyDataSetChanged();那就要大家开发时候自己调理了.

    最后说明一点:网上有好多介绍下拉刷新的例子,但是他们没有对滑动进行处理,比如,我下拉的时候现在不想刷新了,这时我又向上滑动,正常的处理,应该滑动到FirstItemIndex=1就是顶部,滑动就结束了.(意思就是要下拉和listview正常滑动要分开)可是网上一些案例都没有对之处理,用起来不友好,大家可以看看成功案例,那些新浪,腾讯,百度等.

    解决方法:(这是onTouch方法中的一部分.)

    [java] view plaincopy
     
    1. /*** 
    2.          * 如果是ListView本身的拉动,那么返回true,这样ListView不可以拖动. 
    3.          * 如果不是ListView的拉动,那么调用父类方法,这样就可以上拉执行. 
    4.          */  
    5.         if (isScroller) {  
    6.             return super.onTouchEvent(ev);  
    7.         } else {  
    8.             return true;  
    9.         }  

    要问Why的话,那么你就要去详细看Touch种种事件,记住,这里用到的不是分发与拦截,分发拦截流程如下:

    Activity 的dispatchTouchEvent开始分发给子的View,如果该View是ViewGroup的话,那么执行其dispatchTouchEvent进行分发,在执行相应的onInterceptTouchEvent拦截.如果要想实现上诉说的那种效果,那么在自定义ListView中对拦截分发方法是无效的,只有在ListView的上一层进行处理,比我我们在外层自定义一个布局,等等,实现起来总之麻烦一个字,其实我们也可以考虑考虑onTouchEvent事件的实现,

    ListView.java

    [java] view plaincopy
     
    1. @Override  
    2.    public boolean onTouchEvent(MotionEvent ev) {  
    3.        if (mItemsCanFocus && ev.getAction() == MotionEvent.ACTION_DOWN && ev.getEdgeFlags() != 0) {  
    4.            // Don't handle edge touches immediately -- they may actually belong to one of our  
    5.            // descendants.  
    6.            return false;  
    7.        }  
    8.        return super.onTouchEvent(ev);  
    9.    }  

    继续点击查看父类,这里就不显示了,自己可以查看源码,其实就是我们ListView滑动的具体实现,而此时我们只是想临时屏蔽掉此滑动,那么我们只需要不调用父类的onTouchEvent不就OK的,是的,确实如此,而何时进行屏蔽,大家就仔细看上面源码实现吧,解释的也很清楚,这样大家都明白了吧。注:有疑问请留言!之前这个例子android 自定义ScrollView实现反弹效果(以及解决和ListView之间的冲突)没有解决这个问题,因为处境不同.(不过正在完善,相信也会完美的实现这些效果,因为原理上是行的通的。)

    知识拓展:

    首先我们还是看一些案例:

          

    效果就是可以上下拖拽.而用在最多的地方就是ListView,而普通的布局拖拽直接自定义布局就OK了,详情请参考上面连接那篇文章.

    实现起来也不是很麻烦,就是对上面那个自定义类稍作修改,把底部也做成动态拖拽效果就OK了.

    这里不详细讲解,因为注释相当明确,如有疑问,请指出.

    代码如下:

    [java] view plaincopy
     
    1. package com.jj.drag;  
    2.   
    3. import android.content.Context;  
    4. import android.os.AsyncTask;  
    5. import android.util.AttributeSet;  
    6. import android.util.Log;  
    7. import android.view.LayoutInflater;  
    8. import android.view.MotionEvent;  
    9. import android.view.View;  
    10. import android.view.View.OnClickListener;  
    11. import android.view.ViewGroup;  
    12. import android.view.animation.Animation;  
    13. import android.view.animation.LinearInterpolator;  
    14. import android.view.animation.RotateAnimation;  
    15. import android.widget.AbsListView;  
    16. import android.widget.AbsListView.OnScrollListener;  
    17. import android.widget.ImageView;  
    18. import android.widget.LinearLayout;  
    19. import android.widget.ListView;  
    20. import android.widget.ProgressBar;  
    21. import android.widget.RelativeLayout;  
    22. import android.widget.TextView;  
    23.   
    24. /*** 
    25.  * 自定义拖拉ListView 
    26.  *  
    27.  * @author zhangjia 
    28.  *  
    29.  */  
    30. public class DragListView extends ListView implements OnScrollListener,  
    31.         OnClickListener {  
    32.     // 下拉ListView枚举所有状态  
    33.     private enum DListViewState {  
    34.         LV_NORMAL, // 普通状态  
    35.         LV_PULL_REFRESH, // 下拉状态(为超过mHeadViewHeight)  
    36.   
    37.     }  
    38.   
    39.     // 点击加载更多枚举所有状态  
    40.     private enum DListViewLoadingMore {  
    41.         LV_NORMAL, // 普通状态  
    42.         LV_PULL_REFRESH, // 上拉状态(为超过mHeadViewHeight)  
    43.     }  
    44.   
    45.     private View mHeadView, mFootView;// 头部headView  
    46.   
    47.     private int mHeadViewWidth; // headView的宽(mHeadView)  
    48.     private int mHeadViewHeight;// headView的高(mHeadView)  
    49.   
    50.     private int mFirstItemIndex = -1;// 当前视图能看到的第一个项的索引  
    51.   
    52.     private int mLastItemIndex = -1;// 当前视图中是否是最后一项.  
    53.   
    54.     // 用于保证startY的值在一个完整的touch事件中只被记录一次  
    55.     private boolean mIsRecord = false;// 针对下拉  
    56.   
    57.     private boolean mIsRecord_B = false;// 针对上拉  
    58.   
    59.     private int mStartY, mMoveY;// 按下是的y坐标,move时的y坐标  
    60.   
    61.     private DListViewState mlistViewState = DListViewState.LV_NORMAL;// 拖拉状态.(自定义枚举)  
    62.   
    63.     private DListViewLoadingMore loadingMoreState = DListViewLoadingMore.LV_NORMAL;// 加载更多默认状态.  
    64.   
    65.     private final static int RATIO = 2;// 手势下拉距离比.  
    66.   
    67.     private boolean isScroller = true;// 是否屏蔽ListView滑动。  
    68.   
    69.     private MyAsynTask myAsynTask;// 任务  
    70.     private final static int DRAG_UP = 1, DRAG_DOWN = 2;  
    71.   
    72.     public DragListView(Context context) {  
    73.         super(context, null);  
    74.         initDragListView(context);  
    75.     }  
    76.   
    77.     public DragListView(Context context, AttributeSet attrs) {  
    78.         super(context, attrs);  
    79.         initDragListView(context);  
    80.     }  
    81.   
    82.     /*** 
    83.      * 初始化ListView 
    84.      */  
    85.     public void initDragListView(Context context) {  
    86.   
    87.         initHeadView(context);// 初始化该head.  
    88.   
    89.         initFooterView(context);// 初始化footer  
    90.   
    91.         setOnScrollListener(this);// ListView滚动监听  
    92.     }  
    93.   
    94.     /*** 
    95.      * 初始话头部HeadView 
    96.      *  
    97.      * @param context 
    98.      *            上下文 
    99.      * @param time 
    100.      *            上次更新时间 
    101.      */  
    102.     public void initHeadView(Context context) {  
    103.         mHeadView = LayoutInflater.from(context).inflate(R.layout.head, null);  
    104.         measureView(mHeadView);  
    105.         // 获取宽和高  
    106.         mHeadViewWidth = mHeadView.getMeasuredWidth();  
    107.         mHeadViewHeight = mHeadView.getMeasuredHeight();  
    108.   
    109.         addHeaderView(mHeadView, null, false);// 将初始好的ListView add进拖拽ListView  
    110.         // 在这里我们要将此headView设置到顶部不显示位置.  
    111.         mHeadView.setPadding(0, -1 * mHeadViewHeight, 0, 0);  
    112.   
    113.     }  
    114.   
    115.     /*** 
    116.      * 初始化底部加载更多控件 
    117.      */  
    118.     private void initFooterView(Context context) {  
    119.         mFootView = LayoutInflater.from(context).inflate(R.layout.head, null);  
    120.         addFooterView(mFootView, null, false);// 将初始好的ListView add进拖拽ListView  
    121.         // 在这里我们要将此FooterView设置到底部不显示位置.  
    122.         mFootView.setPadding(0, -1 * mHeadViewHeight, 0, 0);  
    123.     }  
    124.   
    125.     /*** 
    126.      * 作用:测量 headView的宽和高. 
    127.      *  
    128.      * @param child 
    129.      */  
    130.     private void measureView(View child) {  
    131.         ViewGroup.LayoutParams p = child.getLayoutParams();  
    132.         if (p == null) {  
    133.             p = new ViewGroup.LayoutParams(ViewGroup.LayoutParams.FILL_PARENT,  
    134.                     ViewGroup.LayoutParams.WRAP_CONTENT);  
    135.         }  
    136.         int childWidthSpec = ViewGroup.getChildMeasureSpec(0, 0 + 0, p.width);  
    137.         int lpHeight = p.height;  
    138.         int childHeightSpec;  
    139.         if (lpHeight > 0) {  
    140.             childHeightSpec = MeasureSpec.makeMeasureSpec(lpHeight,  
    141.                     MeasureSpec.EXACTLY);  
    142.         } else {  
    143.             childHeightSpec = MeasureSpec.makeMeasureSpec(0,  
    144.                     MeasureSpec.UNSPECIFIED);  
    145.         }  
    146.         child.measure(childWidthSpec, childHeightSpec);  
    147.     }  
    148.   
    149.     /*** 
    150.      * touch 事件监听 
    151.      */  
    152.     @Override  
    153.     public boolean onTouchEvent(MotionEvent ev) {  
    154.   
    155.         switch (ev.getAction()) {  
    156.         // 按下  
    157.         case MotionEvent.ACTION_DOWN:  
    158.             doActionDown_B(ev);  
    159.             doActionDown(ev);  
    160.             break;  
    161.         // 移动  
    162.         case MotionEvent.ACTION_MOVE:  
    163.             doActionMove_B(ev);  
    164.             doActionMove(ev);  
    165.             break;  
    166.         // 抬起  
    167.         case MotionEvent.ACTION_UP:  
    168.             doActionUp_B(ev);  
    169.             doActionUp(ev);  
    170.             break;  
    171.         default:  
    172.             break;  
    173.         }  
    174.   
    175.         /*** 
    176.          * 如果是ListView本身的拉动,那么返回true,这样ListView不可以拖动. 
    177.          * 如果不是ListView的拉动,那么调用父类方法,这样就可以上拉执行. 
    178.          */  
    179.         if (isScroller) {  
    180.             return super.onTouchEvent(ev);  
    181.         } else {  
    182.             return true;  
    183.         }  
    184.   
    185.     }  
    186.   
    187.     /*** 
    188.      * 摁下操作 
    189.      *  
    190.      * 作用:获取摁下是的y坐标 
    191.      *  
    192.      * @param event 
    193.      */  
    194.     void doActionDown(MotionEvent event) {  
    195.         // 如果是第一项且是一次touch  
    196.         if (mIsRecord == false && mFirstItemIndex == 0) {  
    197.             mStartY = (int) event.getY();  
    198.             mIsRecord = true;  
    199.         }  
    200.     }  
    201.   
    202.     /*** 
    203.      * 摁下操作 底部 
    204.      *  
    205.      * 作用:获取摁下是的y坐标 
    206.      */  
    207.     void doActionDown_B(MotionEvent event) {  
    208.         // 如果是第一项且是一次touch  
    209.         if (mIsRecord_B == false && mLastItemIndex == getCount()) {  
    210.             mStartY = (int) event.getY();  
    211.             mIsRecord_B = true;  
    212.         }  
    213.     }  
    214.   
    215.     /*** 
    216.      * 拖拽移动操作 
    217.      *  
    218.      * @param event 
    219.      */  
    220.     void doActionMove(MotionEvent event) {  
    221.   
    222.         // 判断是否是第一项,若不是直接返回  
    223.         mMoveY = (int) event.getY();// 获取实时滑动y坐标  
    224.   
    225.         // 检测是否是一次touch事件.  
    226.         if (mIsRecord == false && mFirstItemIndex == 0) {  
    227.             mStartY = (int) event.getY();  
    228.             mIsRecord = true;  
    229.         }  
    230.         // 直接返回说明不是第一项  
    231.         if (mIsRecord == false)  
    232.             return;  
    233.   
    234.         // 向下啦headview移动距离为y移动的一半.(比较友好)  
    235.         int offset = (mMoveY - mStartY) / RATIO;  
    236.   
    237.         switch (mlistViewState) {  
    238.         // 普通状态  
    239.         case LV_NORMAL: {  
    240.             // 说明下拉  
    241.             if (offset > 0) {  
    242.                 // 设置headView的padding属性.  
    243.                 mHeadView.setPadding(0, offset - mHeadViewHeight, 0, 0);  
    244.                 mlistViewState = DListViewState.LV_PULL_REFRESH;// 下拉状态  
    245.             }  
    246.         }  
    247.             break;  
    248.         // 下拉状态  
    249.         case LV_PULL_REFRESH: {  
    250.             setSelection(0);// 时时保持在顶部.  
    251.             // 设置headView的padding属性.  
    252.             mHeadView.setPadding(0, offset - mHeadViewHeight, 0, 0);  
    253.             if (offset < 0) {  
    254.                 /*** 
    255.                  * 要明白为什么isScroller = false; 
    256.                  */  
    257.                 isScroller = false;  
    258.                 mlistViewState = mlistViewState.LV_NORMAL;  
    259.             }  
    260.         }  
    261.             break;  
    262.         default:  
    263.             return;  
    264.         }  
    265.     }  
    266.   
    267.     void doActionMove_B(MotionEvent event) {  
    268.         mMoveY = (int) event.getY();// 获取实时滑动y坐标  
    269.         // 检测是否是一次touch事件.(若mFirstItemIndex为0则要初始化mStartY)  
    270.         if (mIsRecord_B == false && mLastItemIndex == getCount()) {  
    271.             mStartY = (int) event.getY();  
    272.             mIsRecord_B = true;  
    273.         }  
    274.         // 直接返回说明不是最后一项  
    275.         if (mIsRecord_B == false)  
    276.             return;  
    277.   
    278.         // 向下啦headview移动距离为y移动的一半.(比较友好)  
    279.         int offset = (mMoveY - mStartY) / RATIO;  
    280.   
    281.         switch (loadingMoreState) {  
    282.         // 普通状态  
    283.         case LV_NORMAL: {  
    284.             // 说明上拉  
    285.             if (offset < 0) {  
    286.                 int distance = Math.abs(offset);  
    287.                 // 设置headView的padding属性.  
    288.                 mFootView.setPadding(0, distance - mHeadViewHeight, 0, 0);  
    289.                 loadingMoreState = loadingMoreState.LV_PULL_REFRESH;// 下拉状态  
    290.             }  
    291.         }  
    292.             break;  
    293.         // 上拉状态  
    294.         case LV_PULL_REFRESH: {  
    295.             setSelection(getCount() - 1);// 时时保持最底部  
    296.             // 设置headView的padding属性.  
    297.             int distance = Math.abs(offset);  
    298.             mFootView.setPadding(0, distance - mHeadViewHeight, 0, 0);  
    299.             // 说明下滑  
    300.             if (offset > 0) {  
    301.                 /*** 
    302.                  * 要明白为什么isScroller = false; 
    303.                  */  
    304.                 isScroller = false;  
    305.                 loadingMoreState = loadingMoreState.LV_NORMAL;  
    306.             }  
    307.         }  
    308.             break;  
    309.         default:  
    310.             return;  
    311.         }  
    312.     }  
    313.   
    314.     /*** 
    315.      * 手势抬起操作 
    316.      *  
    317.      * @param event 
    318.      */  
    319.     public void doActionUp(MotionEvent event) {  
    320.         mIsRecord = false;// 此时的touch事件完毕,要关闭。  
    321.         mIsRecord_B = false; // 此时的touch事件完毕,要关闭。  
    322.         isScroller = true;// ListView可以Scrooler滑动.  
    323.         mlistViewState = mlistViewState.LV_NORMAL;// 状态也回归最初状态  
    324.   
    325.         // 执行相应动画.  
    326.         myAsynTask = new MyAsynTask();  
    327.         myAsynTask.execute(DRAG_UP);  
    328.   
    329.     }  
    330.   
    331.     private void doActionUp_B(MotionEvent event) {  
    332.         mIsRecord = false;// 此时的touch事件完毕,要关闭。  
    333.         isScroller = true;// ListView可以Scrooler滑动.  
    334.   
    335.         loadingMoreState = loadingMoreState.LV_NORMAL;// 状态也回归最初状态  
    336.   
    337.         // 执行相应动画.  
    338.         myAsynTask = new MyAsynTask();  
    339.         myAsynTask.execute(DRAG_DOWN);  
    340.     }  
    341.   
    342.     /*** 
    343.      * ListView 滑动监听 
    344.      */  
    345.     @Override  
    346.     public void onScrollStateChanged(AbsListView view, int scrollState) {  
    347.   
    348.     }  
    349.   
    350.     @Override  
    351.     public void onScroll(AbsListView view, int firstVisibleItem,  
    352.             int visibleItemCount, int totalItemCount) {  
    353.         mFirstItemIndex = firstVisibleItem;  
    354.         mLastItemIndex = firstVisibleItem + visibleItemCount;  
    355.   
    356.     }  
    357.   
    358.     @Override  
    359.     public void onClick(View v) {  
    360.   
    361.     }  
    362.   
    363.     /*** 
    364.      * 用于产生动画 
    365.      *  
    366.      * @author zhangjia 
    367.      *  
    368.      */  
    369.     private class MyAsynTask extends AsyncTask<Integer, Integer, Void> {  
    370.         private final static int STEP = 30;// 步伐  
    371.         private final static int TIME = 5;// 休眠时间  
    372.         private int distance;// 距离(该距离指的是:mHeadView的PaddingTop+mHeadView的高度,及默认位置状态.)  
    373.         private int number;// 循环执行次数.  
    374.         private int disPadding;// 时时padding距离.  
    375.         private int DRAG;  
    376.   
    377.         @Override  
    378.         protected Void doInBackground(Integer... params) {  
    379.             try {  
    380.                 this.DRAG = params[0];  
    381.                 if (params[0] == DRAG_UP) {  
    382.                     // 获取距离.  
    383.                     distance = mHeadView.getPaddingTop()  
    384.                             + Math.abs(mHeadViewHeight);  
    385.                 } else {  
    386.                     // 获取距离.  
    387.                     distance = mFootView.getPaddingTop()  
    388.                             + Math.abs(mHeadViewHeight);  
    389.                 }  
    390.   
    391.                 // 获取循环次数.  
    392.                 if (distance % STEP == 0) {  
    393.                     number = distance / STEP;  
    394.                 } else {  
    395.                     number = distance / STEP + 1;  
    396.                 }  
    397.                 // 进行循环.  
    398.                 for (int i = 0; i < number; i++) {  
    399.                     Thread.sleep(TIME);  
    400.                     publishProgress(STEP);  
    401.                 }  
    402.             } catch (InterruptedException e) {  
    403.                 e.printStackTrace();  
    404.             }  
    405.             return null;  
    406.         }  
    407.   
    408.         @Override  
    409.         protected void onProgressUpdate(Integer... values) {  
    410.             super.onProgressUpdate(values);  
    411.   
    412.             switch (DRAG) {  
    413.             case DRAG_UP:  
    414.                 disPadding = Math.max(mHeadView.getPaddingTop() - STEP, -1  
    415.                         * mHeadViewHeight);  
    416.                 mHeadView.setPadding(0, disPadding, 0, 0);// 回归.  
    417.                 break;  
    418.             case DRAG_DOWN:  
    419.                 disPadding = Math.max(mFootView.getPaddingTop() - STEP, -1  
    420.                         * mHeadViewHeight);  
    421.                 mFootView.setPadding(0, disPadding, 0, 0);// 回归.  
    422.                 break;  
    423.             default:  
    424.                 break;  
    425.             }  
    426.   
    427.         }  
    428.   
    429.     }  
    430.   
    431. }  

    运行效果:

                                 

             默认效果                                             下拉拖拽效果(会自动回缩)               上拉拖拽效果(会自动回缩)           

    前面那章实现起来有点小BUG,正在处理,不过这个实现起来没有发现什么BUG,要说BUG的话,那么就是优化,因为我觉得上面效果是实现了,可是性能觉得有点差,比如说“我每次UP的时候要执行任务,那么就要创建任务对象,你想想看,每次执行都要创建,那么要创建多少对象,虽说JAVA虚拟机会自动回收,但是总觉得不是很完善,嗯,临时就如此了,自己在研究研究看.

    至于微信,陌陌等大多数应用都是(数据少的话,就上下都可以拖拽,只是一个人性效果,而数据多的话,上部用于加载过时数据.下部只是个形式.),效果实现起来也不难,只是进行了些判断,效果嘛,其实上面自定义ListView整理下就OK了.

    上面我详细给出了两个自定义源码的实现,大家可以直接引用.

    在这里我将源码上传,如果上面看的不明白的话,你可以下载,只要你耐心看,我相信大家都能弄明白,都会进行响应扩展的.其实我们要的就是创新,而不是简单应用.

    源码一

     

    源码二

    就说到这里,如有疑问请留言。

    另外,如果对您有帮助的话,记得赞一个哦.

    在此:Thanks for you !

  • 相关阅读:
    喜马拉雅第三方客户端开发(接口和接口数据解析)。
    jquery-easyui中datagrid扩展,隐藏显示表头功能
    backbone ,jQuery-easyui,knockoutjs的整合使用
    WPF中的瀑布流布局(TilePanel)控件
    使用this.$refs['formName'].resetFields()无法重置表单
    js获取json对象的key值
    Hash表算法详解
    Redis入门
    ASP.Net 下载大文件的实现
    后端生成二维码
  • 原文地址:https://www.cnblogs.com/duanweishi/p/4302291.html
Copyright © 2020-2023  润新知