仿百度外卖下拉刷新新番

看了Hankkin这位老哥写的文章 ,我抱着试一试的态度copy下,原文链接http://blog.csdn.net/lyhhj/article/details/51120539。写完后下载了最新版的百度外卖app,发现其刷新已经做了改动,但是万变不离其宗,精髓依旧,然后新番出炉。

·points:

1.动画
·· 帧动画:一张张图片不断的切换,形成动画效果。在drawable文件夹下放入图片,并创建animation-list标签文件
·· 补间动画:通过在两个关键帧之间补充渐变的动画效果。
JAVA代码中设置:AlphaAnimation,TranslateAnimation,ScaleAnimation,RotateAnimation,AnimationSet;
XML文件中设置:在res目录下新建anim文件夹,在anim文件夹下创建对应的动画文件alpha,rotate,scale,translate,set。
ps:补间动画只是改变了View对象绘制的位置,而没有改变View对象本身
·· 属性动画:既有动画效果又使得View本身得到了真正改变。
JAVA代码中设置: ObjectAnimator-ofFloat(),ofInt(),ofObject(),ofArgb(),ofPropertyValuesHolder();
XML文件中设置:和补间动画基本一致,Animator anim = AnimatorInflater.loadAnimator(this, R.animator.animator);

2.onTouchEvent事件的处理
··MotionEvent.ACTION_DOWN:
记录按下点的Y坐标。
··MotionEvent.ACTION_MOVE:
通过.setPadding()更新下拉刷新视图的显示高度,并通过offsetY来记录其状态。
··MotionEvent.ACTION_UP:
对两种状态进行处理:没有达到刷新高度的回滚至隐藏,达到或超过其刷新高度的回滚至刷新的高度,并回调接口更新状态。

·talk is less:

1.layout_header_view布局(有坑也要小心)

    <RelativeLayout
        xmlns:android="http://schemas.android.com/apk/res/android"
        android:layout_width="match_parent"
        android:layout_height="wrap_content">
    
        <RelativeLayout
            android:layout_width="match_parent"
            android:layout_height="350dp"
            android:background="@drawable/pull_default_background">
    
            <ImageView
                android:id="@+id/iv_castle_left"
                android:layout_width="match_parent"
                android:layout_height="150dp"
                android:layout_alignParentBottom="true"
                android:padding="30dp"
                android:src="@drawable/pull_back_castle"/>
    
            <ImageView
                android:id="@+id/iv_castle_right"
                android:layout_width="match_parent"
                android:layout_height="150dp"
                android:layout_alignParentBottom="true"
                android:padding="30dp"
                android:src="@drawable/pull_back_castle"/>
    
            <ImageView
                android:id="@+id/iv_cloud_left"
                android:layout_width="match_parent"
                android:layout_height="150dp"
                android:layout_alignParentBottom="true"
                android:padding="30dp"
                android:src="@drawable/pull_back_cloud"/>
    
            <ImageView
                android:id="@+id/iv_cloud_right"
                android:layout_width="match_parent"
                android:layout_height="150dp"
                android:layout_alignParentBottom="true"
                android:padding="30dp"
                android:src="@drawable/pull_back_cloud"/>
    
            <RelativeLayout
                android:layout_width="match_parent"
                android:layout_height="wrap_content"
                android:layout_alignParentBottom="true"
                android:layout_marginTop="50dp">
    
                <ImageView
                    android:id="@+id/iv_island"
                    android:layout_width="120dp"
                    android:layout_height="100dp"
                    android:layout_centerHorizontal="true"
                    android:layout_marginTop="30dp"
                    android:src="@drawable/pull_main_castle"/>
    
                <ImageView
                    android:id="@+id/iv_postman"
                    android:layout_width="100dp"
                    android:layout_height="70dp"
                    android:layout_alignLeft="@id/iv_island"
                    android:layout_marginTop="10dp"
                    android:background="@drawable/loading_bear"/>
    
                <ImageView
                    android:id="@+id/iv_sun"
                    android:layout_width="25dp"
                    android:layout_height="25dp"
                    android:layout_toLeftOf="@id/iv_island"
                    android:src="@drawable/pull_back_sun"/>
            </RelativeLayout>
        </RelativeLayout>
    </RelativeLayout>
layout_header_view.png

2.TakeRefreshLayout

    public class TakeRefreshLayout extends ListView implements AbsListView.OnScrollListener {
        private static final int PULL_FINISH = 0;   //刷新完毕
        private static final int PULL_TO_DOWN = 1;  //下拉状态
        private static final int PULL_TO_RELEASE = 2;   //释放状态
        private static final int PULL_REFRESHING = 3;   //正在刷新
    
        private Context mContext;
        private RelativeLayout mHeadView;    //刷新的头布局
        private int mHeadViewH;     //头布局的高度
        private int mFirstVisibleItem;  //第一项可见的item索引
        private ImageView ivDriver, ivCloudLeft, ivCloudRight, ivCastleLeft, ivCastleRight, ivSun;
        private Animation sunAnim, cloudLAnim, cloudRAnim, castleLAnim, castleRAnim;
        private AnimationDrawable driverAnim;
    
        private float startY;   //开始时的Y坐标
        private float offsetY;  //Y轴偏移量
    
        private int state;  //状态
        private boolean isRefreshable;  //是否可刷新
        private OnTakeRefreshListener mTakeRefreshListener;
    
        public TakeRefreshLayout(Context context) {
            super(context);
            init(context);
        }
    
        public TakeRefreshLayout(Context context, AttributeSet attrs) {
            super(context, attrs);
            init(context);
        }
    
        public TakeRefreshLayout(Context context, AttributeSet attrs, int defStyleAttr) {
            super(context, attrs, defStyleAttr);
            init(context);
        }
    
        /**
         * 回调接口
         */
        public interface OnTakeRefreshListener {
            void onRefresh();
        }
    
        /**
         * 接口回调提供的方法
         *
         * @param onTakeRefreshListener
         */
        public void setOnTakeRefreshListener(OnTakeRefreshListener onTakeRefreshListener) {
            mTakeRefreshListener = onTakeRefreshListener;
            isRefreshable = true;
        }
    
        /**
         * 是否正在刷新
         *
         * @return
         */
        public boolean isRefreshing() {
            return state == PULL_REFRESHING;
        }
    
        /**
         * 刷新完成
         */
        public void finishRefresh() {
            state = PULL_FINISH;
            //刷新完毕
            mHeadView.setPadding(0, -mHeadViewH, 0, 0);
            stopAnim();
        }
    
        /**
         * 初始化
         *
         * @param context
         */
        private void init(Context context) {
            mContext = context;
            //关闭view的OverScroll
            setOverScrollMode(OVER_SCROLL_NEVER);
            setOnScrollListener(this);
            //加载头布局
            mHeadView = (RelativeLayout) LayoutInflater.from(context).inflate(R.layout.layout_head_view, this, false);
            //测量头布局
            measureView(mHeadView);
            //测量头布局
            addHeaderView(mHeadView);
            mHeadViewH = mHeadView.getMeasuredHeight();
            mHeadView.setPadding(0, -mHeadViewH, 0, 0);
    
            //获取头布局控件
            ivDriver = (ImageView) mHeadView.findViewById(R.id.iv_postman);
            ivCastleLeft = (ImageView) mHeadView.findViewById(R.id.iv_castle_left);
            ivCastleRight = (ImageView) mHeadView.findViewById(R.id.iv_castle_right);
            ivCloudLeft = (ImageView) mHeadView.findViewById(R.id.iv_cloud_left);
            ivCloudRight = (ImageView) mHeadView.findViewById(R.id.iv_cloud_right);
            ivSun = (ImageView) mHeadView.findViewById(R.id.iv_sun);
    
            //获取动画
            driverAnim = (AnimationDrawable) ivDriver.getBackground();
            sunAnim = AnimationUtils.loadAnimation(context, R.anim.rotate_sun);
            cloudLAnim = AnimationUtils.loadAnimation(context, R.anim.translate_cloud_left);
            cloudRAnim = AnimationUtils.loadAnimation(context, R.anim.translate_cloud_right);
            castleLAnim = AnimationUtils.loadAnimation(context, R.anim.translate_castle_left);
            castleRAnim = AnimationUtils.loadAnimation(context, R.anim.translate_castle_right);
    
            LinearInterpolator interpolator = new LinearInterpolator();
            sunAnim.setInterpolator(interpolator);
            cloudLAnim.setInterpolator(interpolator);
            cloudRAnim.setInterpolator(interpolator);
            castleLAnim.setInterpolator(interpolator);
            castleRAnim.setInterpolator(interpolator);
    
            state = PULL_FINISH;
            isRefreshable = false;
        }
    
        @Override
        public boolean onTouchEvent(MotionEvent ev) {
            if (isRefreshable) {
                switch (ev.getAction()) {
                    case MotionEvent.ACTION_DOWN:
                        //在listview顶部
                        if (mFirstVisibleItem == 0) {
                            startY = ev.getY();
                        }
                        break;
                    case MotionEvent.ACTION_MOVE:
                        if (state != PULL_REFRESHING) {
                            float tempY = ev.getY();
                            offsetY = tempY - startY;
                            //放开刷新的状态
                            if (state == PULL_TO_RELEASE) {
                                setSelection(0);
                                if (offsetY / 3 < DensityUtil.dip2px(mContext, 150)) {
                                    state = PULL_TO_DOWN;
                                } else if (offsetY <= 0) {
                                    state = PULL_FINISH;
                                }
                            }
                            //下拉刷新状态
                            if (state == PULL_TO_DOWN) {
                                setSelection(0);
                                if (offsetY / 3 > DensityUtil.dip2px(mContext, 150)) {
                                    state = PULL_TO_RELEASE;
                                } else if (offsetY <= 0) {
                                    state = PULL_FINISH;
                                }
                            }
                            //finish状态
                            if (state == PULL_FINISH) {
                                if (offsetY >= 0) {
                                    state = PULL_TO_DOWN;
                                    startAnim();
                                }
                            }
                            //需要更新视图的两种状态
                            if (state == PULL_TO_DOWN) {
                                mHeadView.setPadding(0, (int) (-mHeadViewH + offsetY / 3), 0, 0);
                            }
                            if (state == PULL_TO_RELEASE) {
                                mHeadView.setPadding(0, (int) (-mHeadViewH + offsetY / 3), 0, 0);
                            }
                        }
                        break;
                    case MotionEvent.ACTION_UP:
                        //下拉没有超过规定的高度,不执行刷新,抬起手指更新state,停止动画
                        if (state == PULL_TO_DOWN) {
                            smoothScrollBy((int) offsetY / 3, 500);
                            stopAnim();
                            state = PULL_FINISH;
                        }
                        //当下拉高度超过150dp时执行平滑滚动到150dp并刷新
                        if (state == PULL_TO_RELEASE) {
                            if (offsetY / 3 > DensityUtil.dip2px(mContext, 150)) {
                                smoothScrollBy((int) (offsetY / 3 - DensityUtil.dip2px(mContext, 150)), 500);
                            }
                            state = PULL_REFRESHING;
                            //回调onRefresh方法
                            mTakeRefreshListener.onRefresh();
                        }
                        break;
                    default:
                        break;
                }
            }
            return super.onTouchEvent(ev);
        }
    
        /**
         * 开始动画
         */
        private void startAnim() {
            ivSun.startAnimation(sunAnim);
            ivCloudLeft.startAnimation(cloudLAnim);
            ivCloudRight.startAnimation(cloudRAnim);
            ivCastleLeft.startAnimation(castleLAnim);
            ivCastleRight.startAnimation(castleRAnim);
            driverAnim.start();
        }
    
        /**
         * 关闭动画
         */
        private void stopAnim() {
            ivSun.clearAnimation();
            ivCloudLeft.clearAnimation();
            ivCloudRight.clearAnimation();
            ivCastleLeft.clearAnimation();
            ivCastleRight.clearAnimation();
            driverAnim.stop();
        }
    
        /**
         * 测量View
         *
         * @param child
         */
        private void measureView(View child) {
            ViewGroup.LayoutParams params = child.getLayoutParams();
            if (params == null) {
                params = new ViewGroup.LayoutParams(ViewGroup.LayoutParams.MATCH_PARENT, ViewGroup.LayoutParams.WRAP_CONTENT);
            }
            int childWidthSpec = ViewGroup.getChildMeasureSpec(0, child.getPaddingLeft(), params.width);
            int pHeight = params.height;
            int childHeightSpec;
            if (pHeight > 0) {
                childHeightSpec = MeasureSpec.makeMeasureSpec(pHeight, MeasureSpec.EXACTLY);
            } else {
                childHeightSpec = MeasureSpec.makeMeasureSpec(0, MeasureSpec.UNSPECIFIED);
            }
            child.measure(childWidthSpec, childHeightSpec);
        }
    
        @Override
        public void onScrollStateChanged(AbsListView view, int scrollState) {
    
        }
    
        @Override
        public void onScroll(AbsListView view, int firstVisibleItem, int visibleItemCount, int totalItemCount) {
            mFirstVisibleItem = firstVisibleItem;
        }
    }

3.MainActivity

    public class MainActivity extends AppCompatActivity implements TakeRefreshLayout.OnTakeRefreshListener {
        private final static int REFRESH_COMPLETE = 0;
    
        private TakeRefreshLayout mRefreshLayout;
        private List<String> mDatas;
        private ArrayAdapter<String> mAdapter;
        private Handler mHandler = new Handler() {
            public void handleMessage(android.os.Message msg) {
                switch (msg.what) {
                    case REFRESH_COMPLETE:
                        if (mRefreshLayout.isRefreshing()) {
                            mRefreshLayout.finishRefresh();
                        }
                        Toast.makeText(MainActivity.this, "刷新成功", Toast.LENGTH_SHORT).show();
                        mAdapter.notifyDataSetChanged();
                        mRefreshLayout.setSelection(0);
                        break;
    
                    default:
                        break;
                }
            }
        };
    
        @Override
        protected void onCreate(Bundle savedInstanceState) {
            super.onCreate(savedInstanceState);
            setContentView(R.layout.activity_main);
    
            mRefreshLayout = (TakeRefreshLayout) findViewById(R.id.take_refresh_view);
            String[] data = new String[]{"a", "b", "c", "d", "e", "f", "g", "h", "i",
                    "j", "k", "l", "m", "n", "o", "p", "q", "r", "s"};
            mDatas = new ArrayList<>(Arrays.asList(data));
            mAdapter = new ArrayAdapter<>(this, android.R.layout.simple_list_item_1, mDatas);
            mRefreshLayout.setAdapter(mAdapter);
            mRefreshLayout.setOnTakeRefreshListener(this);
        }
    
        @Override
        public void onRefresh() {
            mDatas.add(0, "new data");
            mHandler.sendEmptyMessageDelayed(REFRESH_COMPLETE, 4000);
        }
    }
show_time.png

·总结

敲一次代码就应该有所收货,每一次进步都应该玩的不亦乐乎。
就总结一下这个下拉刷新版本的不足吧:
1.没有多点触控
2.仅能用在ListView,不能无缝替换RecyclerView
3....

最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 212,080评论 6 493
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 90,422评论 3 385
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 157,630评论 0 348
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 56,554评论 1 284
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 65,662评论 6 386
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 49,856评论 1 290
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 39,014评论 3 408
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 37,752评论 0 268
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 44,212评论 1 303
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 36,541评论 2 327
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 38,687评论 1 341
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 34,347评论 4 331
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 39,973评论 3 315
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 30,777评论 0 21
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 32,006评论 1 266
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 46,406评论 2 360
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 43,576评论 2 349

推荐阅读更多精彩内容