Android自定义View——滑动冲突

1.该自定义View是一个ViewGroup,由主界面和滑动界面Drawer组成,如图所示:

自定义View

View完整代码如下

public class SlideMenuextends ViewGroup {

    private View leftMenu;
    private View mainView;
    private float moveX;
    private float downX;
    private int curScrollPosition;
    private int cur_state = 0;//当前状态
    private static final int MAIN_STATE = 0;
    private static final int MENU_STATE = 1;
    private Scroller scroller;//滚动器
    private float downY;

    public SlideMenu(Context context) {
        super(context);
        initAnim();
    }
    
    public SlideMenu(Context context, AttributeSet attrs) {
        super(context, attrs);
        initAnim();
    }
    
    public SlideMenu(Context context, AttributeSet attrs, int defStyleAttr) {
        super(context, attrs, defStyleAttr);
        initAnim();
    }
    //初始化滚动器
    private void initAnim() {
        scroller = new Scroller(getContext());
    }
    
    /**
     * 测量并设置所有子View宽高
     * @param widthMeasureSpec:当前控件的宽度测量规则
     * @param heightMeasureSpec:当前控件的高度测量规则
     */
    @Override
    protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
        //指定左面板的宽高
        leftMenu = getChildAt(0);//第一个子View
        //leftMenu.measure(leftMenu.getMeasuredWidth(), heightMeasureSpec);
        leftMenu.measure(leftMenu.getLayoutParams().width, heightMeasureSpec);
        //指定主面板的宽高
        mainView = getChildAt(1);//第二个子View
        mainView.measure(widthMeasureSpec, heightMeasureSpec);
        super.onMeasure(widthMeasureSpec, heightMeasureSpec);
    }

    /**
     * b:当前控件的尺寸大小、位置是否发生了变化
     */
    @Override
    protected void onLayout(boolean isChanged, int l, int t, int r, int b) {
        //放置在侧滑面板(相对于左上角原点位置)
        leftMenu.layout(-leftMenu.getMeasuredWidth(), 0, 0, b);
        mainView.layout(l, t, r, b);
    }

    /**
     *事件拦截
     */
    @Override
    public boolean onInterceptTouchEvent(MotionEvent event) {
        switch (event.getAction()) {
            case MotionEvent.ACTION_DOWN:
                downX = event.getX();
                downY = event.getY();
                break;
            case MotionEvent.ACTION_MOVE:
                //根据偏移量判断是否拦截
                float offsetX = Math.abs(event.getX() - downX);
                float offsetY = Math.abs(event.getY() - downY);
                if (offsetX > offsetY && offsetX > 5) {
                    return true;//拦截触摸事件,不往里传递,交个自己的OnTouchEvent处理
                }
                break;
            case MotionEvent.ACTION_UP:
                break;
        }
        return super.onInterceptTouchEvent(event);//默认不拦截
    }

    /**
     * 滚动屏幕,将View看做静的,屏幕是动的
     * scrollBy(x,y) :在原来位置基础上滚动了
     * scrollTo(x,y) : 滚动到(x,y)位置
     */
    @Override
    public boolean onTouchEvent(MotionEvent event) {

        switch (event.getAction()) {
            case MotionEvent.ACTION_DOWN:
                downX = event.getX();//按下时X坐标
                break;
            case MotionEvent.ACTION_MOVE:
                moveX = event.getX();//
                int x_change = (int) (downX - moveX);//x变化(偏移量)
                //当前要滚动的位置 = 之前滚动到的位置 + 偏移量
                curScrollPosition = getScrollX() + x_change;//最新滚动位置
                /**
                 *   考虑边界问题
                 */
                if (curScrollPosition > 0) {
                    scrollTo(0, 0);
                } else if (curScrollPosition < -leftMenu.getMeasuredWidth()) {
                    scrollTo(-leftMenu.getMeasuredWidth(), 0);
                } else {
                    scrollBy(x_change, 0);//(在范围内)滚动到当前位置
                }
                downX = moveX;//重要
                break;
            case MotionEvent.ACTION_UP:
                int menu_center = (int) (-leftMenu.getMeasuredWidth() / 2.0f);
                if (getScrollX() < menu_center) {
                    //打开
                    cur_state = MENU_STATE;
                } else {
                    cur_state = MAIN_STATE;
                }
                updateView(cur_state);
                break;
        }
        return true;//一定改成true(消费事件)
    }

    //滑动过程中松开手
    private void updateView(int cur_state) {
        int startX = getScrollX();//负数(当前滑动到的位置)
        int dx = 0;//将要移动的偏移量
        if (cur_state == MAIN_STATE) {//关闭
            dx = 0 - startX;
        } else {
            dx = -leftMenu.getMeasuredWidth() - startX;
        }
        //开始平滑的数据模拟
        int duration = Math.abs(dx * 2);
        //执行动画
        scroller.startScroll(startX, 0, dx, 0, duration);
        invalidate();//重绘界面:drawChild--computeScroll()
    }

    //维持动画
    @Override
    public void computeScroll() {
        if (scroller.computeScrollOffset()) {//true动画没结束时(duration时间内)一直调用
            int currX = scroller.getCurrX();//获取当前的模拟值,要滚动的位置
            scrollTo(currX, 0);
            invalidate();//重绘->drawChild--computeScroll()
        }
    }
    //手动设置关闭菜单栏
    public void switcheDrawer() {
        if (cur_state == MAIN_STATE) {//主
            cur_state = MENU_STATE;
        } else {
            cur_state = MAIN_STATE;
        }
        updateView(cur_state);
    }
}

2.自定义View布局

该view是一个ViewGroup,里面有两个子View

    <!-- 这是一个ViewGroup ,里面两个子View-->
    <com.sliding.SlideMenu
        android:id="@+id/sm"
        android:layout_width="wrap_content"
        android:layout_height="match_parent">
   		<!-- 第一个子View-->
        <include layout="@layout/layout_menu_left"/>
   		<!--第两个子View-->
        <include layout="@layout/layout_main" />

    </com.sliding.SlideMenu>

3.测量子View宽高

对ViewGroup所有子View的宽高进行测量

  @Override
  protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
      //测量第一个子View的宽高
      leftMenu = getChildAt(0);
      leftMenu.measure(leftMenu.getLayoutParams().width, heightMeasureSpec);
      //测量第二个子View的宽高
      mainView = getChildAt(1);
      mainView.measure(widthMeasureSpec, heightMeasureSpec);
      super.onMeasure(widthMeasureSpec, heightMeasureSpec);
  }

4.放置每个子View

	@Override
	protected void onLayout(boolean isChanged, int l, int t, int r, int b) {
	    //第一个子View的位置
	    leftMenu.layout(-leftMenu.getMeasuredWidth(), 0, 0, b);
	    //第二个子View的位置
	    mainView.layout(l, t, r, b);
	}

5.滑动过程中的触摸事件(重要)

滚动屏幕,将View看做静的,屏幕是动的
scrollBy(x,y) :在原来位置基础上滚动了(x,y)
scrollTo(x,y) : 滚动到(x,y)位置

  @Override
  public boolean onTouchEvent(MotionEvent event) {

      switch (event.getAction()) {
          case MotionEvent.ACTION_DOWN:
              downX = event.getX();//按下时X坐标
              break;
          case MotionEvent.ACTION_MOVE:
              moveX = event.getX();//
              int x_change = (int) (downX - moveX);//x变化(偏移量)
              //当前要滚动的位置 = 之前滚动到的位置 + 偏移量
              curScrollPosition = getScrollX() + x_change;//最新滚动位置
              /**
               *   考虑边界问题
               */
              if (curScrollPosition > 0) {
                  scrollTo(0, 0);
              } else if (curScrollPosition < -leftMenu.getMeasuredWidth()) {
                  scrollTo(-leftMenu.getMeasuredWidth(), 0);
              } else {
                  scrollBy(x_change, 0);//(在范围内)滚动到当前位置
              }
              downX = moveX;//重要点
              break;
          case MotionEvent.ACTION_UP:
              int menu_center = (int) (-leftMenu.getMeasuredWidth() / 2.0f);
              if (getScrollX() < menu_center) {
                  cur_state = MENU_STATE;
              } else {
                  cur_state = MAIN_STATE;
              }
              updateView(cur_state);
              break;
      }
      return true;//一定改成true(消费事件)
  }

最后松手时,需要让View的Drawer处于开或者闭的状态

    //滑动过程中松开手
    private void updateView(int cur_state) {
        int startX = getScrollX();//负数(当前滑动到的位置)
        int dx = 0;//将要移动的偏移量
        if (cur_state == MAIN_STATE) {//关闭
            dx = 0 - startX;
        } else {
            dx = -leftMenu.getMeasuredWidth() - startX;
        }
        int duration = Math.abs(dx * 2);//动画执行时间可以自行设置
        //Android自带的滚动器执行动画
        scroller.startScroll(startX, 0, dx, 0, duration);
        invalidate();//重绘界面:drawChild--computeScroll()
    }

    //维持动画
    @Override
    public void computeScroll() {
        if (scroller.computeScrollOffset()) {//true动画没结束时(duration时间内)一直调用
            int currX = scroller.getCurrX();//获取当前的模拟值,要滚动的位置
            scrollTo(currX, 0);
            invalidate();//重绘->drawChild--computeScroll()
        }
    }

6.事件冲突问题

由于Drawer是一个ScrollView,在上下滑动与左右滑动会有事件冲突,这时候需要对事件传递进行拦截处理,交给自己的OnTouchEvent()处理,否则继续传给子View处理

    @Override
    public boolean onInterceptTouchEvent(MotionEvent event) {
        switch (event.getAction()) {
            case MotionEvent.ACTION_DOWN:
                downX = event.getX();
                downY = event.getY();
                break;
            case MotionEvent.ACTION_MOVE:
                //根据偏移量判断是否拦截
                float offsetX = Math.abs(event.getX() - downX);
                float offsetY = Math.abs(event.getY() - downY);
                //当左右滑动偏移量大于上下滑动偏移量,且偏移量大于5时(),进行拦截处理
                if (offsetX > offsetY && offsetX > 5) {
                    return true;//拦截触摸事件,不往里传递,交个自己的OnTouchEvent处理
                }
                break;
            case MotionEvent.ACTION_UP:
                break;
        }
        return super.onInterceptTouchEvent(event);//默认返回false不拦截
    }

7.程序入口

public class MainActivity extends AppCompatActivity{
    
    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_main);
        final SlideMenu slideMenu = findViewById(R.id.sm);
        ImageButton imageButton= findViewById(R.id.main_back);
        imageButton.setOnClickListener(new View.OnClickListener() {
            @Override
            public void onClick(View v) {
                if (v.getId()==R.id.main_back){
	                 //手动设置开关菜单栏
	                 slideMenu.switcheDrawer();
                }
            }
        });
    }
}

欢迎大佬批评指正!

发布了28 篇原创文章 · 获赞 1 · 访问量 528

猜你喜欢

转载自blog.csdn.net/qq_40575302/article/details/104533855