zoukankan      html  css  js  c++  java
  • 结合支付宝和微信首页巩固android事件分发机制 (附项目源码)

    结合支付宝和微信首页巩固android事件分发机制

    文章出处大黑的博客--事件分发

    源码地址 https://github.com/halibobo/TouchListenerConflict 欢迎star

    android的事件分发和处理方式

    对android开发有一定了解的同学一定或多或少知道android的触摸事件分发,整个事件的分发消耗流程都可以通过看源码理解,下面通过讲解demo帮助加深事件分发的理解和在实战中的应用。首先直接上demo截图:

    enter image description here

    demo布局

    整个首页布局是这样的,最外层是ViewPager,里面包含四个子功能,每个子功能的视图都是一个Fragment。“功能1”里的列表项是一个GridView,此gridview外层是带有LinearLayout的ScrollView。
    布局如下
    enter image description here

    难点分析和讲解

    一、GridView高度问题

    二、长按GridView某一项可以替换位置,最后一项“更多”不参与滑动与替换位置

    三、GridView长按并滑动某一项时,滑动过程中与ScrollView和ViewPager冲突问题

    四、滑动GridView中某一项时,当滑动到顶部时ScrollView要能向下滚动;手指滑动到底部时要能项上滚动

    解决问题

    解决问题一

    为了解决这一系列问题,我需要重写DragGridView继承GridView,第一步需要让DragGridView的每一项item占满整个视图,而gridview默认是限定高度的,解决办法是重写onMeasure,修改测量高度方法,如下

     @Override
    protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
        int expandSpec = View.MeasureSpec.makeMeasureSpec(
                Integer.MAX_VALUE >> 2, View.MeasureSpec.AT_MOST);//1.精确模式(MeasureSpec.EXACTLY) 2.最大模式(MeasureSpec.AT_MOST) 3.未指定模式(MeasureSpec.UNSPECIFIED)
        super.onMeasure(widthMeasureSpec, expandSpec);
    }
    

    解决问题二

    现在我们需要长按GridView某一项可以替换位置,思路是首先需要捕捉到长按事件,那么怎么算长按事件呢?手指快速的从item上划是不算的,手指按下然后很快离开这是点击事件也不能算长按。只有手指在item上按下并且停留在item上一定时间才能算长按。有了思路,下面看关键的几处代码:

    private Handler mHandler = new Handler();
    //用来处理是否为长按的Runnable
    private Runnable mLongClickRunnable = new Runnable() {
    
        @Override
        public void run() {
        //todo
        }
    };
    
    @Override
    public boolean dispatchTouchEvent(MotionEvent ev) {
        switch(ev.getAction()){
            case MotionEvent.ACTION_DOWN:
                //使用Handler延迟dragResponseMS执行mLongClickRunnable
                mHandler.postDelayed(mLongClickRunnable, dragResponseMS);
                break;
            case MotionEvent.ACTION_MOVE:
                //如果我们在按下的item上面移动,只要不超过item的边界我们就不移除mRunnable
                if(!isTouchInItem(mStartDragItemView, moveX, moveY)){
                    mHandler.removeCallbacks(mLongClickRunnable);
                    mHandler.removeCallbacks(mScrollRunnable);
                }
    
                break;
            case MotionEvent.ACTION_UP:
            case MotionEvent.ACTION_CANCEL:
                mHandler.removeCallbacks(mLongClickRunnable);
                break;
        }
        return super.dispatchTouchEvent(ev);
    }
    

    解析: 定义一个Handler,重写ViewGroup(这里即DragGridView)分发事件方法dispatchTouchEvent(),在接受到ACTION DOWN事件时handler向消息队列会延迟发送一条消息,延迟时间就是长按时间的阀值,当接受到消息时说明长按事件已成立,如果手指在消息延迟期间滑走或移出则取消消息。

    有了长按事件如何滑动某一条item呢?如何替换两个item位置呢?
    这里的解决思路是,当其中的某项假设是item1接收到长按事件后,先隐藏item1,在item1的位置上新建一个和item1长相位置都一样view假设叫它litem,手指滑动时对litem跟随滑动,接着重点来了,当手指滑动到item2区域后发出一个替换事件。这样我们就成功将两个item替换了位置并且用户体验比较好,代码片段如下:

     //用来处理是否为长按的Runnable
    private Runnable mLongClickRunnable = new Runnable() {
    
        @Override
        public void run() {
            if (onDragStartListener != null) {
                onDragStartListener.onDragStart();
            }
            isDrag = true; //设置可以拖拽
            mVibrator.vibrate(50); //震动一下
            mStartDragItemView.setVisibility(View.INVISIBLE);//隐藏该item
            //根据我们按下的点显示item镜像
            createDragImage(mDragBitmap, mDownX, mDownY);
    
    
        }
    };
    /**
     * 设置替换回调接口
     * @param onChangeListener
     */
    public void setOnChangeListener(OnChangeListener onChangeListener){
        this.onChangeListener = onChangeListener;
    }
    /**
     * 创建拖动的镜像
     * @param bitmap
     * @param downX
     *          按下的点相对父控件的X坐标
     * @param downY
     *          按下的点相对父控件的X坐标
     */
    private void createDragImage(Bitmap bitmap, int downX , int downY){
        mWindowLayoutParams = new WindowManager.LayoutParams();
        mWindowLayoutParams.format = PixelFormat.TRANSLUCENT; //图片之外的其他地方透明
        mWindowLayoutParams.gravity = Gravity.TOP | Gravity.LEFT;
        mWindowLayoutParams.x = downX - mPoint2ItemLeft + mOffset2Left;
        mWindowLayoutParams.y = downY - mPoint2ItemTop + mOffset2Top - mStatusHeight;
        mWindowLayoutParams.alpha = 0.55f; //透明度
        mWindowLayoutParams.width = WindowManager.LayoutParams.WRAP_CONTENT;
        mWindowLayoutParams.height = WindowManager.LayoutParams.WRAP_CONTENT;
        mWindowLayoutParams.flags = WindowManager.LayoutParams.FLAG_NOT_FOCUSABLE
                | WindowManager.LayoutParams.FLAG_NOT_TOUCHABLE ;
    
        mDragImageView = new ImageView(getContext());
        mDragImageView.setImageBitmap(bitmap);
        mWindowManager.addView(mDragImageView, mWindowLayoutParams);
    }
    @Override
    public boolean onTouchEvent(MotionEvent ev) {
        if(isDrag && mDragImageView != null){
            switch(ev.getAction()){
                case MotionEvent.ACTION_MOVE:
                    moveX = (int) ev.getX();
                    moveY = (int) ev.getY();
                    moveRawY = (int) ev.getRawY();
                    //拖动item
                    onDragItem(moveX, moveY);
                    break;
                case MotionEvent.ACTION_UP:
                    onStopDrag();
                    isDrag = false;
                    break;
            }
            return true;
        }
        return super.onTouchEvent(ev);
    }
    

    解析:在接收到长按事件后会调用 createDragImage(mDragBitmap, mDownX, mDownY)方法,这是为了在item的位置上创建一个长相一样的view,当手指滑动时在onTouch()方法中监听move事件处理view的位置

    解决问题三

    为题三的解决比较简单,当gridview接收到长按事件后,处理ViewPager和ScrollView的方法一样requestDisallowInterceptTouchEvent(false); 这个方法是让ViewGroup本身不拦截触摸事件。当gridView滑动结束在requestDisallowInterceptTouchEvent(true);

    解决问题四

    直接上源码

    public class ControlScrollView extends ScrollView {
    
    private boolean isInControl = true;
    private int moveSpeed = 5;
    private final int msgWhat = 1;
    private final int time = 20;
    private ScrollState scrollState;
    
    public ControlScrollView(Context context) {
        super(context);
        init();
    }
    
    public ControlScrollView(Context context, AttributeSet attrs) {
        super(context, attrs);
        init();
    }
    
    @Override
    public boolean dispatchTouchEvent(MotionEvent ev) {
        switch (ev.getAction()) {
            case MotionEvent.ACTION_MOVE:/**/
                if (!isInControl) {
                    if (ev.getY() < 0) {
                        if (!myHandler.hasMessages(msgWhat)) {
                            Message msg = new Message();
                            msg.arg1 = -1;
                            msg.what = msgWhat;
                            myHandler.sendMessageDelayed(msg, time);
                        }
                        return super.dispatchTouchEvent(ev);
                    } else if (ev.getY() > getHeight()) {
                        if (!myHandler.hasMessages(msgWhat)) {
                            Message msg = new Message();
                            msg.arg1 = 1;
                            msg.what = msgWhat;
                            myHandler.sendMessageDelayed(msg, time);
                        }
                        return super.dispatchTouchEvent(ev);
                    } else {
                        myHandler.removeMessages(msgWhat);
                    }
                } else {
                    myHandler.removeMessages(msgWhat);
                }
                break;
            case MotionEvent.ACTION_UP:
            case MotionEvent.ACTION_CANCEL:
                if (scrollState != null) {
                    scrollState.stopTouch();
                }
                myHandler.removeMessages(msgWhat);
                requestDisallowInterceptTouchEvent(false);
                break;
        }
        return super.dispatchTouchEvent(ev);
    }
    
    
    @Override
    protected void onDetachedFromWindow() {
        super.onDetachedFromWindow();
        myHandler.removeMessages(msgWhat);
    }
    
    
    private void init() {
        moveSpeed = ScreenUtils.dip2px(getContext(), moveSpeed);
    }
    
    public boolean isInControl() {
        return isInControl;
    }
    
    public ScrollState getScrollState() {
        return scrollState;
    }
    
    public void setScrollState(ScrollState scrollState) {
        this.scrollState = scrollState;
    }
    
    public void setInControl(boolean inControl) {
        isInControl = inControl;
    }
    
    private Handler myHandler = new Handler() {
        @Override
        public void dispatchMessage(Message msg) {
            super.dispatchMessage(msg);
            smoothScrollBy(0, moveSpeed * (msg.arg1 > 0 ? 1 : -1));
            Message msg1 = new Message();
            msg1.what = msg.what;
            msg1.arg1 = msg.arg1;
            myHandler.sendMessageDelayed(msg1, time);
        }
    };
    
    public interface ScrollState {
        void stopTouch();
    }
    

    }

    解析:大致逻辑就是判断手指位置,超出边界发送消息对scrollview进行滚动

    结束

    源码地址 https://github.com/halibobo/TouchListenerConflict 欢迎star
    wp开发 metro开发
  • 相关阅读:
    函数
    循环练习
    循环结构
    分支结构
    C语言关键字
    进制编码
    MAC/Xcode简单操作命令
    Hibernate
    Hibernate
    Hibernate
  • 原文地址:https://www.cnblogs.com/daheihei/p/5564190.html
Copyright © 2011-2022 走看看