仿美团下拉刷新控件(二)

版权声明:本文为博主原创文章,未经博主允许不得转载。 https://blog.csdn.net/qq_32059827/article/details/78376396

如果想学习更多进阶知识,可以关注我的微信公众号:Android小菜。

也可以直接扫描二维码关注:


转载本专栏文章,请注明出处,尊重原创 。文章博客地址:道龙的博客


本篇是实现仿美团下拉刷新控件的第二篇,第一篇见:仿美团下拉刷新控件(一)

文本最终实现效果如下:

这里写图片描述 

由于之前写过三篇自定义下拉刷新控件的文章,而且当时写的非常非常的细,本篇就不再浪费大家时间了,直接把重要的地方做一些解释,感兴趣的朋友可以直接下载源码。

1、创建一个自定义View类RefreshListView继承自ListView。

此时这个RefreshListView可以当做一个ListView来使用了。

2、在构造方法中初始化头布局

/**
 * 初始化头
 */
private void initHearder() {
    mHeaderView = LayoutInflater.from(mContext).inflate(R.layout.layout_header,null);
    mFirstStepView = (RefreshFirstStepView) mHeaderView.findViewById(R.id.first_view);
    mSecondStepView = (RefreshSecondStepView) mHeaderView.findViewById(R.id.second_view);
    mThirdStepView = (RefreshThirdStepView) mHeaderView.findViewById(R.id.third_view);
    mTvDes = (TextView) mHeaderView.findViewById(R.id.tv_pull_to_refresh);

    mHeaderView.measure(0,0);

    mHeaderHeight = mHeaderView.getMeasuredHeight();
    //        Log.e(TAG,"头高度为:--->"+mHeaderHeight);

    mHeaderView.setPadding(0,-mHeaderHeight,0,0);

    addHeaderView(mHeaderView);
}

看到这里初始化了一个布局,布局样式:

<?xml version="1.0" encoding="utf-8"?>
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
              android:layout_width="match_parent"
              android:layout_height="wrap_content"
              android:gravity="center"
              android:orientation="horizontal"

    >
    <com.itydl.a05.view.RefreshFirstStepView
        android:id="@+id/first_view"
        android:layout_width="45dp"
        android:layout_height="45dp"
        android:layout_marginTop="10dp"
        android:layout_marginBottom="5dp"
        />

    <com.itydl.a05.view.RefreshSecondStepView
        android:id="@+id/second_view"
        android:layout_width="45dp"
        android:visibility="gone"
        android:layout_height="45dp"
        android:layout_marginTop="10dp"
        android:layout_marginBottom="5dp"
        />

    <com.itydl.a05.view.RefreshThirdStepView
        android:id="@+id/third_view"
        android:layout_width="45dp"
        android:layout_height="45dp"
        android:visibility="gone"
        android:layout_marginTop="10dp"
        android:layout_marginBottom="5dp"
        />

    <TextView
        android:id="@+id/tv_pull_to_refresh"
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:layout_marginLeft="10dp"
        android:text="下拉刷新"
        />

</LinearLayout>

实现下拉刷新,肯定是要加一个头布局的嘛,这个头布局就作为下拉刷新的布局。通过打气筒拿到对应的View实例,然后拿到它的孩子控件,它里面有三个孩子控件,这三个孩子控件都是通过自定义View的方式引入的,具体的实现可见仿美团下拉刷新控件(一)
从上往下一次代表:下拉刷新状态展示()的布局,释放刷新状态展示的布局,正在刷新状态展示的布局。最底部的TextView就代表当前状态的文案。往下调用头布局的测量方法拿到头布局的高度,设置padding值后,就默认状态下把头布局隐藏起来了。然后通过addHeaderView()的方式把头布局添加到自定义的ListView当中:

3、重写onTouchEvent事件,处理交互:

    @Override
    public boolean onTouchEvent(MotionEvent ev) {

        switch (ev.getAction()) {
                case MotionEvent.ACTION_DOWN:
                    downY = ev.getY();
                    break;
                case MotionEvent.ACTION_MOVE:

                    if(downY == -1){
                        downY = ev.getY();
                    }

                    float moveY = ev.getY();
                    //如果当前处于下拉刷新状态,不去走下面的代码
                    if(currentSatae == REFRESHING){
                        return false;//如果当前处于正在刷新状态,取消后续的所有事件包括上滑和下滑
                    }

                    float instance = (moveY - downY)/3;
                    if(getFirstVisiblePosition() == 0 && instance >= 0){//0item且下拉
                        int topPadding = (int) (-mHeaderHeight + instance +0.5f);//计算padding值
                        if(topPadding > -mHeaderHeight){//只有padding值大于-mHeaderHeight的时候才去自己处理事件
                            if(topPadding < 0 && currentSatae != PULL_TO_REFRESH){
//                                Log.e(TAG,"下拉刷新状态");
                                currentSatae = PULL_TO_REFRESH;
                            }else if(topPadding >= 0 && currentSatae != RELEASE_TO_REFRESH){
//                                Log.e(TAG,"释放刷新状态");
                                currentSatae = RELEASE_TO_REFRESH;
                            }
                        }
                        mHeaderView.setPadding(0,topPadding,0,0);
                        refreshState(instance);
                        return true;//自己处理事件
                    }

                    break;
                case MotionEvent.ACTION_UP:
                    downY = -1;
                    if(currentSatae == RELEASE_TO_REFRESH){
                        //当前状态是释放刷新,松手后是正在刷新状态
                        currentSatae = REFRESHING;
                        //还原Padding,使用Scroll平滑移动过去
                        mHeaderView.setPadding(0,0,0,0);
                        refreshState(mHeaderHeight);
                        if(mOnRefreshListener != null){
                            mOnRefreshListener.onRefreshing();
                        }
                    }else if(currentSatae == PULL_TO_REFRESH){
                        //使用Scroll平滑移动过去
                        mHeaderView.setPadding(0,-mHeaderHeight,0,0);
                    }
                    break;

                default:
                    break;
         }

        return super.onTouchEvent(ev);//ListView的事件保留
    }

对这段代码解释一下:

在按下的时候记录一个坐标,downY这个值初始化值为-1,当move的时候如果这个值为-1,说明down事件没有获取到当前的坐标Y值(是有可能发生的),所以在move的时候先判断downY是否拿到没有拿到这个值就在move的时候记录第一个值。然后往下,判断当前处于什么状态,如果是下拉刷新状态,就不走任何逻辑了返回了false表示后续的事件都拿不到。然后MOVE事件里面拿到移动的距离,只有是手指move位置相对初识位置downY插值是大于零的表示头布局往下拉了一些并且当前处于第0个Item的时候才去走自己的逻辑处理下拉刷新状态。从而就确定了当前状态。随着移动的距离计算,如果超过了头布局的高度时候属于释放刷新状态,少于头布局的高度为下拉刷新状态。然后记录下当前状态,并且通过设置padding值改变头布局的位置,最后调用了refreshState(instance);方法来根据当前的状态值刷新控件的样式以及动画的加载等,把(moveY-downY)/3这个值传过去,因为待会要用到它计算百分比。(moveY-downY)/3的另一个原因是不让下拉头布局过多的距离。

然后是UP事件,如果UP的时候处于下拉刷新状态,那么就恢复原来的状态,这里使用很暴力的方式直接设置padding了。如果当前是释放刷新状态松手应该是进入正在刷新状态了,正在刷新状态的时候就要调用接口方法了,在这个方法里面进行具体的耗时操作,比如访问网络最新数据等。然后在调用refreshState(instance);方法来根据当前的状态值刷新控件的样式以及动画的加载。

看一下refreshState(instance);这个方法:

/**
     * 刷新当前的状态
     * 根据标记为处理控件的样式数据
     * @param instance
     */
    private void refreshState(float instance) {

        //计算比例
        float moveY = Math.abs(instance);

        float percent = moveY / mHeaderHeight;
        if(percent >= 1.0f){
            percent = 1.0f;
        }
        Log.e(TAG,"百分比-->"+percent);

        switch (currentSatae) {
            case PULL_TO_REFRESH:
                mFirstStepView.setVisibility(VISIBLE);
                mSecondStepView.setVisibility(GONE);
                mThirdStepView.setVisibility(GONE);
                mTvDes.setText("下拉刷新");
                //执行动画
                mFirstStepView.setProgress(percent);
                mFirstStepView.postInvalidate();
                break;
            case RELEASE_TO_REFRESH:
                mFirstStepView.setVisibility(GONE);
                mSecondStepView.setVisibility(VISIBLE);
                mThirdStepView.setVisibility(GONE);
                mTvDes.setText("释放刷新");
                secondAnim.start();
                break;
            case REFRESHING:
                mFirstStepView.setVisibility(GONE);
                mSecondStepView.setVisibility(GONE);
                mThirdStepView.setVisibility(VISIBLE);
                mTvDes.setText("正在刷新");
                mThreeAnim.start();
                break;

            default:
                break;
            }
    }

通过判断不同的状态来做对应的控件处理,无非就是某个状态展示对应状态的动画,以及文案的修改。这里讲一下最开始计算百分数:

        //计算比例
        float moveY = Math.abs(instance);

        float percent = moveY / mHeaderHeight;
        if(percent >= 1.0f){
            percent = 1.0f;
        }
手指移动距离/头的高度-->0.0f-->1.0f,比如头高度是100,移动了20,那么percent=0.2f以此类推。但是在下拉的时候moveY可能会超过头的高度,如果超过了,就按照1.0f处理。


4、使用自定义下拉刷新控件

在Activity或者Fragment中:

public class TestActivity extends AppCompatActivity implements RefreshListView.OnRefreshListener {

    private static final int REFRESH_COMPLETE = 0;
    private RefreshListView mListView;
    private ArrayList<String> mDatas;

    /**
     * MyHandler是一个私有静态内部类继承自Handler,内部持有MainActivity的弱引用,
     * 避免内存泄露
     */
    private MyHandler mHandler = new MyHandler(this);

    private ArrayAdapter<String> mAdapter;

    private static class MyHandler extends Handler{

        private WeakReference<TestActivity> mActivity;
        public MyHandler(TestActivity activity){
            mActivity = new WeakReference<TestActivity>(activity);
        }

        @Override
        public void handleMessage(Message msg) {
            TestActivity activity = mActivity.get();
            if (activity != null) {
                switch (msg.what) {
                    case REFRESH_COMPLETE:
                        activity.mListView.setOnRefreshComplete();
                        activity.mAdapter.notifyDataSetChanged();
                        activity.mListView.setSelection(0);
                        break;
                }
            }else{
                super.handleMessage(msg);
            }
        }
    }

    @Override
    protected void onCreate(@Nullable Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_test);
        mListView = (RefreshListView) findViewById(R.id.lv_refresh);
        mDatas = new ArrayList<String>(Arrays.asList(Strings.NAMES));
        mAdapter = new ArrayAdapter<>(this, android.R.layout.simple_list_item_1, mDatas);
        mListView.setAdapter(mAdapter);
        mListView.setOnRefreshListener(this);
    }

    @Override
    public void onRefreshing() {
        //开启子线程访问网络
        new Thread(new Runnable() {
            @Override
            public void run() {
                SystemClock.sleep(3000);
                mDatas.add(0, "new data");
                mHandler.sendEmptyMessage(REFRESH_COMPLETE);
            }
        }).start();
    }

}
直接在刷新的方法中进行耗时操作,进行访问网络的功能,以及成功之后添加数据功能,然后使用Handler发送消息,在处理消息的方法中进行数据的刷新。值得注意的是,这里为了防止内存泄漏做了一点点操作,相信都能明白是什么意思,因为之前文章也都说到过如何避免内存泄漏问题。

然后运行程序:



这样这个控件就讲完了,如果觉得对你有帮助记得加下关注哈,或者微信关注公众号——Android小菜。











猜你喜欢

转载自blog.csdn.net/qq_32059827/article/details/78376396