自定义下拉刷新控件(自定义ViewGroup)

上次写了自定义View,这次在项目中看到项目中的下拉刷新控件觉得很有趣便想模仿效果写一个出来,想了很多实现方案,最后选择自定义ViewGroup来实现。思路很简单,就是通过自定义一个ViewGroup,第一个View用来放我们的header,即下拉时要展现的View,第二个View则是我们需要展示的内容,可以是RecyclerView,也可以是我们想展现的内容ViewGroup。自定义ViewGroup需要重写两个方法,onMeasure和onLayout,其中onMeasure()是用来测量我们ViewGroup的大小以及内部子View的大小,onLayout则是控制我们的布局,即子控件的摆放位置。下面看代码:

public class Mypulltoview extends ViewGroup {
    Scroller scroller;
    int mylastmove = 0;
    int myDown = 0;
    int mymove = 0;
    boolean isfirst = true;
    RefreshListener listener;//刷新监听器
    int symbolline = 0;  //记录下拉滑动的距离,只有headerview完全展示时才开始刷新

    public void setListener(RefreshListener listener) {
        this.listener = listener;
    }


    public Mypulltoview(Context context) {
        super(context);
        scroller = new Scroller(context);
    }

    public Mypulltoview(Context context, AttributeSet attrs) {
        super(context, attrs);
        scroller = new Scroller(context);
    }

    public Mypulltoview(Context context, AttributeSet attrs, int defStyleAttr) {
        super(context, attrs, defStyleAttr);
    }
    //onMeasure函数,若我们的控件都是固定值或match_parent时,系统提供了默认的实现方式,如果有wrap_content的情况,我们必须要重写该函数
    @Override
    protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
        super.onMeasure(widthMeasureSpec, heightMeasureSpec);
        measureChildren(widthMeasureSpec, heightMeasureSpec);//即对子view进行测量,如果没有这一句,我们的子View不会显示。
    }

    @Override
    protected void onLayout(boolean b, int i, int i1, int i2, int i3) {
        //开始布局,headerview不显示,下拉时才显示
        View view = getChildAt(0);
        view.layout(0, -view.getMeasuredHeight(), view.getMeasuredWidth(), 0);
        View view1 = getChildAt(1);
        view1.layout(0, 0, view1.getMeasuredWidth(), view1.getMeasuredHeight());
    }
   //重点,onInterceptTouchEvent负责拦截触摸事件
    @Override
    public boolean onInterceptTouchEvent(MotionEvent ev) {
        switch (ev.getAction()) {
            case MotionEvent.ACTION_DOWN:
                myDown = (int) ev.getRawY();
                break;
            case MotionEvent.ACTION_MOVE:
                mymove = (int) ev.getRawY();
  //判断当前内容View是否还能继续向上滑动,利用ViewCompat的canScrollVertically判断view是否还能在竖直方向滑动,对应还有canScrollHorizontal
                boolean a = getChildAt(1).canScrollVertically(-1);
                Log.d("tag", "move");
//如过是下拉,并且内容View不能继续向上滑动,拦截Move事件
                if (myDown - mymove < 0 && !a) {
                    //Log.d("tag",getChildAt(1).getTop()+"");
                    mylastmove=mymove;
                    return true;
                }
                break;
            case MotionEvent.ACTION_UP:
                Log.d("tag", "释放");
        }
        return super.onInterceptTouchEvent(ev);
    }

    @Override
    public boolean onTouchEvent(MotionEvent event) {
        switch (event.getAction()) {
            case MotionEvent.ACTION_MOVE:
                mymove = (int) event.getRawY();
                int scrolly = mylastmove - mymove;
                symbolline += Math.abs(scrolly / 2);
                Log.d("tag", symbolline + "滑动距离");
                Log.d("tag", getChildAt(0).getMeasuredHeight() + "第一个view高度");
                scrollBy(0, scrolly / 2);
               //判断下拉距离是否大于headerview的高度,是的话开始刷新动画
                if (symbolline > getChildAt(0).getMeasuredHeight() && isfirst) {
                    listener.startRefresh();
                    isfirst = false;
                }
                mylastmove = mymove;
                break;
            case MotionEvent.ACTION_UP:
                if (!isfirst) {
//用户手指抬起时,需要弹回一段距离,回到足够显示headrview的高度即可
                    scroller.startScroll(0, getScrollY(), 0, -getScrollY() - getChildAt(0).getMeasuredHeight());
                    isfirst = true;
                    listener.startUpdate();
                } else {
                    scroller.startScroll(0, getScrollY(), 0, -getScrollY());
                }
                Log.d("tag", "up" + getScrollY());
                symbolline = 0;
                invalidate();
                break;
            default:
                break;
        }
        return super.onTouchEvent(event);
    }

    @Override
    public void computeScroll() {
        if (scroller.computeScrollOffset()) {
            scrollTo(scroller.getCurrX(), scroller.getCurrY());
            invalidate();
        }
    }
  //必须重写该方法。其实就是不做处理即可,因为在recyclerview中,如果recyclerview监听到自己在滑动时,会调用该父View的该方法来禁用父view拦截事件的功能,这样我们就无法监听了,因此在这里我们不做处理,写为空。
    @Override
    public void requestDisallowInterceptTouchEvent(boolean disallowIntercept) {

    }
//更新成功时调用的方法。
    public void success() {
        scroller.startScroll(0, getScrollY(), 0, -getScrollY());
        Log.d("tag", "up" + getScrollY());
        invalidate();
    }
}

上面就是我们的ViewGroup,注释也写在代码里,接下来看我的刷新view,其实就是一朵小红花不停的旋转。

public class RefreshView extends LinearLayout {
    ImageView mImageView;
    Context mContext;
    ObjectAnimator animator;

    public RefreshView(Context context) {
        super(context);
        init(context);
    }

    public RefreshView(Context context, @Nullable AttributeSet attrs) {
        super(context, attrs);
        init(context);
    }

    public RefreshView(Context context, @Nullable AttributeSet attrs, int defStyleAttr) {
        super(context, attrs, defStyleAttr);
    }

    public void init(Context context) {
        mContext = context;
        this.setGravity(VERTICAL);
        mImageView = new ImageView(mContext);
        mImageView.setImageResource(R.drawable.redflower);
        LayoutParams params = new LinearLayout.LayoutParams(ViewGroup.LayoutParams.WRAP_CONTENT, ViewGroup.LayoutParams.WRAP_CONTENT);
        params.gravity = Gravity.CENTER_HORIZONTAL | Gravity.BOTTOM;
        addView(mImageView, params);
    }

    public void startAnimation() {
        Log.d("tag", "执行动画");
        animator = ObjectAnimator.ofFloat(mImageView, "rotation", 0, 359);
        animator.setDuration(1000);
        animator.setInterpolator(new LinearInterpolator());
        animator.setRepeatCount(10);
        animator.start();
    }

    public void stopAnimation() {
        animator.cancel();
    }

}

利用属性动画来达到旋转的效果,哦需要注意的是为了能够看起来是在不停旋转,没有停顿,我们旋转的角度应该是0——359,并且由于属性动画的旋转动画默认并不是匀速的,因此我们需要使用匀速插值器。在这里有个坑(animator.setRepeatCount()不能设置Interger.MaxValue),我不清楚是为什么,后面在研究一下。
接下来就是MainActivity,主要就是模仿了一个数据刷新的过程。

public class MainActivity extends AppCompatActivity {
    RecyclerView recyclerView;
    List<String> list=new ArrayList<>();
    Mypulltoview mypulltoview;
    RefreshView refreshView;
    Handler handler=new Handler(){
        @Override
        public void handleMessage(Message msg) {
            if (msg.what==1){
                recyclerView.setAdapter(new MyAdapter(list));
                refreshView.stopAnimation();
                mypulltoview.success();
            }
        }
    };


    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_main);
        recyclerView=findViewById(R.id.myrecycler);
        for (int i=0;i<20;i++){
            list.add("小明"+i);
        }
        recyclerView.setLayoutManager(new LinearLayoutManager(this));
        recyclerView.setAdapter(new MyAdapter(list));
        mypulltoview=findViewById(R.id.mypullview);
        refreshView=findViewById(R.id.myrefresh);
        mypulltoview.setListener(new RefreshListener() {
            @Override
            public void startRefresh() {
                refreshView.startAnimation();
            }

            @Override
            public void successrefresh() {
                refreshView.stopAnimation();
                mypulltoview.success();
            }

            @Override
            public void startUpdate() {
                new Thread(new Runnable() {
                    @Override
                    public void run() {
                        try {
                            //更新逻辑
                            Thread.sleep(2000);
                            Collections.reverse(list);//反转list
                            //这里完全可以用runOnUIThread哈,只是我想温习下handler的用法
                            Message message=new Message();
                            message.what=1;
                            handler.sendMessage(message);
                        } catch (InterruptedException e) {
                            e.printStackTrace();
                        }
                    }
                }).start();
            }
        });
    }
    class MyAdapter extends RecyclerView.Adapter{
        List<String> list=new ArrayList<>();
        public MyAdapter(List<String> list){
            this.list=list;
        }
         class ViewHolder extends RecyclerView.ViewHolder {
            TextView textView;
            public ViewHolder(@NonNull View itemView) {
                super(itemView);
                textView=itemView.findViewById(R.id.mytext);
            }
        }

        @NonNull
        @Override
        public RecyclerView.ViewHolder onCreateViewHolder(@NonNull ViewGroup viewGroup, int i) {
            View view= LayoutInflater.from(viewGroup.getContext()).inflate(R.layout.mylayoutitem,viewGroup,false);
            ViewHolder holder=new ViewHolder(view);
            return holder;
        }

        @Override
        public void onBindViewHolder(@NonNull RecyclerView.ViewHolder viewHolder, int i) {
            ViewHolder holder= (ViewHolder) viewHolder;
            holder.textView.setText(list.get(i));
        }

        @Override
        public int getItemCount() {
            return list.size();
        }

    }

    @Override
    protected void onDestroy() {
        super.onDestroy();
        refreshView.stopAnimation();
    }
}

好吧,本篇就到这,虽然有很多坑,但自己写一遍还是收益匪浅。

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

推荐阅读更多精彩内容