美文网首页高级UI
Android--利用DrawerLayout打造自定义侧滑效果

Android--利用DrawerLayout打造自定义侧滑效果

作者: aruba | 来源:发表于2020-03-10 11:13 被阅读0次
    自定义侧滑效果.gif
    上次说到自定义属性在系统控件上的应用,今天继续利用这个思想,基于DrawerLayout打造自己的侧滑效果
    首先看下我们的布局文件
    <?xml version="1.0" encoding="utf-8"?>
    <LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
        xmlns:app="http://schemas.android.com/apk/res-auto"
        xmlns:tools="http://schemas.android.com/tools"
        android:layout_width="match_parent"
        android:layout_height="match_parent"
        tools:context=".MainActivity">
    
        <com.aruba.drawerapplication.MyDrawerLayout
            android:id="@+id/dl_demo"
            android:layout_width="match_parent"
            android:layout_height="match_parent">
    
            <!--contentView-->
            <TextView
                android:layout_width="match_parent"
                android:layout_height="match_parent"
                android:gravity="center"
                android:text="content" />
    
            <!--侧滑view-->
            <com.aruba.drawerapplication.SlideLinearLayout
                android:id="@+id/ll_slide"
                android:layout_width="200dp"
                android:layout_height="match_parent"
                android:layout_gravity="start"
                android:background="@android:color/white"
                android:gravity="center_vertical">
    
                <ImageView
                    android:layout_width="wrap_content"
                    android:layout_height="wrap_content"
                    android:src="@mipmap/ic_launcher_round" />
    
                <TextView
                    style="@style/MenuText"
                    android:layout_marginTop="100dp"
                    android:text="标签1" />
    
                <TextView
                    style="@style/MenuText"
                    android:text="标签2" />
    
                <TextView
                    style="@style/MenuText"
                    android:text="标签3" />
    
                <TextView
                    style="@style/MenuText"
                    android:text="标签4" />
    
            </com.aruba.drawerapplication.SlideLinearLayout>
    
        </com.aruba.drawerapplication.MyDrawerLayout>
    
    </LinearLayout>
    
    除了使用自定义的DrawerLayout和LinearLayout,其他和DrawerLayout使用完全一样,其中自定义DrawerLayout在添加View的时候,对我们的这个LinearLayout进行了一层包裹
    /**
     * 自定义DrawerLayout,里面的自定义LinearLayout自动包裹一层
     */
    public class MyDrawerLayout extends DrawerLayout implements DrawerLayout.DrawerListener {
        //内容view
        private View contentView;
        //实际上的侧滑view
        private SlideRelativeLayout slideRelativeLayout;
        private float fraction;
        private float touchY;
    
        public MyDrawerLayout(@NonNull Context context) {
            this(context, null);
        }
    
        public MyDrawerLayout(@NonNull Context context, @Nullable AttributeSet attrs) {
            super(context, attrs);
            //添加侧滑监听
            addDrawerListener(this);
        }
    
        @Override
        public void addView(View child, ViewGroup.LayoutParams params) {
            //偷梁换柱
            if (child instanceof SlideLinearLayout) {
                slideRelativeLayout = new SlideRelativeLayout(getContext());
                slideRelativeLayout.attachLinearLayout((SlideLinearLayout) child, params);
    
                //将drawerLayout生成的LayoutParams设置给自身
                super.addView(slideRelativeLayout, params);
            } else {
                contentView = child;
                super.addView(child, params);
            }
        }
    
        /**
         * 这里将触摸的y值分发下去
         *
         * @param ev
         * @return
         */
        @Override
        public boolean dispatchTouchEvent(MotionEvent ev) {
            this.touchY = ev.getY();
    
            if (ev.getAction() == MotionEvent.ACTION_UP) {
                slideRelativeLayout.onMotionUp(ev.getRawX(), ev.getRawY());
                if (fraction >= 1) {
                    slideRelativeLayout.setOpen(true);
                } else {
                    slideRelativeLayout.setOpen(false);
                }
            }
    
            //没有打开之前 不拦截 我们在onDrawerSlide回调中处理   
            if (fraction < 1) {
                return super.dispatchTouchEvent(ev);
            } else {//等于1后 onDrawerSlide将不会回调  内容区域不再进行偏移,但是背景的贝塞尔曲线还要随手指触摸的点变化
                if (!slideRelativeLayout.isOpen()) {
                    slideRelativeLayout.setTouchY(touchY, fraction);
                    return true;
                } else {
                    return super.dispatchTouchEvent(ev);
                }
            }
        }
    
    
        /**
         * 这里记录打开比例
         *
         * @param view
         * @param v
         */
        @Override
        public void onDrawerSlide(@NonNull View view, float v) {
            this.fraction = v;
            slideRelativeLayout.setTouchY(touchY, fraction);
            //针对内容区域进行偏移
            float contentViewoffset = getWidth() * fraction / 2;
            contentView.setTranslationX(contentViewoffset);
        }
    
        @Override
        public void onDrawerOpened(@NonNull View view) {
            slideRelativeLayout.setOpen(true);
        }
    
        @Override
        public void onDrawerClosed(@NonNull View view) {
            fraction = 0;
            //点击item后关闭侧滑或者手指快速侧滑时的重置状态
            slideRelativeLayout.setOpen(false);
        }
    
        @Override
        public void onDrawerStateChanged(int i) {
    
        }
    
        public void setItemClickListener(SlideLinearLayout.ItemClickListener itemClickListener) {
            if (slideRelativeLayout != null) {
                slideRelativeLayout.setItemClickListener(itemClickListener);
            }
        }
    }
    
    核心思路看addView方法,之后我们利用包裹一层RelativeLayout来把子控件的摆放和背景的特效分开,子控件摆放交给LinearLayout,背景的特效我们自定义View实现
    /**
     * 包含SlideLinearLayout用的布局
     */
    public class SlideRelativeLayout extends RelativeLayout {
    
        private SlideLinearLayout slideLinearLayout;
        private SlideBackgroundView slideBackgroundView;
        private boolean isOpen;
    
        public SlideRelativeLayout(Context context) {
            this(context, null);
        }
    
        public SlideRelativeLayout(Context context, AttributeSet attrs) {
            super(context, attrs);
        }
    
    
        public void attachLinearLayout(SlideLinearLayout child, ViewGroup.LayoutParams params) {
            this.slideLinearLayout = child;
    
            //添加背景view
            slideBackgroundView = new SlideBackgroundView(getContext());
            slideBackgroundView.setBackground(slideLinearLayout.getBackground());
            slideLinearLayout.setBackground(new ColorDrawable(Color.TRANSPARENT));
            addView(slideBackgroundView, new LayoutParams(ViewGroup.LayoutParams.MATCH_PARENT, ViewGroup.LayoutParams.MATCH_PARENT));
            //添加SlideLinearLayout
            addView(child, new LayoutParams(ViewGroup.LayoutParams.MATCH_PARENT, ViewGroup.LayoutParams.MATCH_PARENT));
        }
    
        /**
         * 触摸事件分发
         *
         * @param y
         * @param fraction
         */
        public void setTouchY(float y, float fraction) {
            if (isOpen) {
                return;
            }
            if (slideBackgroundView != null) {
                slideBackgroundView.calcBezier(y, fraction);
            }
            if (slideLinearLayout != null) {
                slideLinearLayout.layoutChild(y);
            }
        }
    
        /**
         * 手指抬起,并分配点击事件
         */
        public void onMotionUp(float x, float y) {
            if (!isOpen || slideLinearLayout == null || !slideLinearLayout.hasItemClickListener())
                return;
    
            slideLinearLayout.onMotionUp(x, y);
        }
    
        public void setOpen(boolean open) {
            isOpen = open;
        }
    
        public void setItemClickListener(SlideLinearLayout.ItemClickListener itemClickListener) {
            if (slideLinearLayout != null) {
                slideLinearLayout.setItemClickListener(itemClickListener);
            }
        }
    
    }
    
    下面是背景特效的View,利用贝塞尔曲线
    /**
     * 侧滑菜单背景view
     */
    public class SlideBackgroundView extends View {
        private Paint paint;
        private Path path;
        private int height, width;
        //贝塞尔曲线初始坐标和结束坐标的Y轴偏移
        private float offsetY;
        private float offsetX;
    
        public SlideBackgroundView(Context context) {
            this(context, null);
        }
    
        public SlideBackgroundView(Context context, @Nullable AttributeSet attrs) {
            super(context, attrs);
            init();
        }
    
        private void init() {
            paint = new Paint(Paint.ANTI_ALIAS_FLAG);
            path = new Path();
        }
    
        /**
         * 处理path,贝塞尔曲线
         *
         * @param y        触摸的y坐标
         * @param fraction 侧滑打开的比例0-1
         */
        public void calcBezier(float y, float fraction) {
            path.reset();
    
            height = getHeight();
            width = getWidth();
            offsetY = height / 8f;
            offsetX = width / 2f;
            path.moveTo(0, -offsetY);
            path.lineTo(offsetX, -offsetY);
            path.quadTo(width * 3 / 2f * fraction, y, offsetX, height + offsetY);
            path.lineTo(0, height + offsetY);
            path.close();
    
            invalidate();
        }
    
    
        @Override
        protected void onDraw(Canvas canvas) {
            canvas.drawPath(path, paint);
        }
    
        @Override
        public void setBackground(Drawable background) {
            if (background instanceof ColorDrawable) {
                paint.setColor(((ColorDrawable) background).getColor());
            } else if (background instanceof BitmapDrawable) {
                BitmapDrawable bitmapDrawable = (BitmapDrawable) background;
                BitmapShader bitmapShader = new BitmapShader(bitmapDrawable.getBitmap(), Shader.TileMode.CLAMP, Shader.TileMode.CLAMP);
                paint.setShader(bitmapShader);
            }
        }
    }
    
    自定义LinearLayout,根据手指的y坐标,对每个item设置不同的偏移量
    /**
     * 带有对每个child水平偏移效果的LinearLayout
     */
    public class SlideLinearLayout extends LinearLayout {
        //最大偏移量
        private float maxTranslationX;
        private ItemClickListener itemClickListener;
    
        public SlideLinearLayout(Context context) {
            this(context, null);
        }
    
        public SlideLinearLayout(Context context, @Nullable AttributeSet attrs) {
            this(context, attrs, 0);
        }
    
        public SlideLinearLayout(Context context, @Nullable AttributeSet attrs, int defStyleAttr) {
            super(context, attrs, defStyleAttr);
            setOrientation(VERTICAL);
    
            TypedArray typedArray = context.obtainStyledAttributes(attrs, R.styleable.SlideLinearLayout);
            maxTranslationX = typedArray.getDimension(R.styleable.SlideLinearLayout_maxTranslationX, 200);
            typedArray.recycle();
        }
    
        private View child;
        private int childCenterY;
        private float fraction;
    
        /**
         * 对每个child有一个不同的水平偏移
         *
         * @param y
         */
        public void layoutChild(float y) {
            int childCount = getChildCount();
            for (int i = 0; i < childCount; i++) {
                child = getChildAt(i);
                //child的y坐标
                childCenterY = (int) (child.getTop() + child.getHeight() / 2f);
                fraction = Math.abs(childCenterY - y) / getHeight() * 3;//放大系数3
                if (fraction < 1) {
                    child.setTranslationX(maxTranslationX - fraction * maxTranslationX);
                }
            }
        }
    
        /**
         * 点击事件
         *
         * @param y
         */
        public void onMotionUp(float x, float y) {
            if (itemClickListener == null) {
                return;
            }
    
            int position = findChilde(x, y);
            if (position != -1) {
                itemClickListener.onItemClickListener(getChildAt(position), position);
            }
        }
    
        private int findChilde(float x, float y) {
            int childCount = getChildCount();
            for (int i = 0; i < childCount; i++) {
                View child = getChildAt(i);
                Rect r = new Rect();
                child.getGlobalVisibleRect(r);
                if (r.contains((int) x, (int) y)) {
                    return i;
                }
            }
    
            return -1;
        }
    
        public boolean hasItemClickListener() {
            if (itemClickListener == null)
                return false;
    
            return true;
        }
    
        public ItemClickListener getItemClickListener() {
            return itemClickListener;
        }
    
        public void setItemClickListener(ItemClickListener itemClickListener) {
            this.itemClickListener = itemClickListener;
        }
    
        public interface ItemClickListener {
            void onItemClickListener(View view, int position);
        }
    }
    
    项目地址:https://gitee.com/aruba/DrawerLayoutApplication.git

    相关文章

      网友评论

        本文标题:Android--利用DrawerLayout打造自定义侧滑效果

        本文链接:https://www.haomeiwen.com/subject/fmoidhtx.html