自定义侧滑效果.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
Logo

为开发者提供学习成长、分享交流、生态实践、资源工具等服务,帮助开发者快速成长。

更多推荐