前往小程序,Get更优阅读体验!
立即前往
首页
学习
活动
专区
工具
TVP
发布
社区首页 >专栏 >Android--利用DrawerLayout打造自定义侧滑效果

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

作者头像
aruba
发布2020-07-02 15:28:13
7860
发布2020-07-02 15:28:13
举报
文章被收录于专栏:android技术

自定义侧滑效果.gif

上次说到自定义属性在系统控件上的应用,今天继续利用这个思想,基于DrawerLayout打造自己的侧滑效果
首先看下我们的布局文件
代码语言:javascript
复制
<?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进行了一层包裹
代码语言:javascript
复制
/**
 * 自定义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实现
代码语言:javascript
复制
/**
 * 包含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,利用贝塞尔曲线
代码语言:javascript
复制
/**
 * 侧滑菜单背景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设置不同的偏移量
代码语言:javascript
复制
/**
 * 带有对每个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
本文参与 腾讯云自媒体同步曝光计划,分享自作者个人站点/博客。
如有侵权请联系 cloudcommunity@tencent.com 删除

本文分享自 作者个人站点/博客 前往查看

如有侵权,请联系 cloudcommunity@tencent.com 删除。

本文参与 腾讯云自媒体同步曝光计划  ,欢迎热爱写作的你一起参与!

评论
登录后参与评论
0 条评论
热度
最新
推荐阅读
目录
  • 上次说到自定义属性在系统控件上的应用,今天继续利用这个思想,基于DrawerLayout打造自己的侧滑效果
  • 首先看下我们的布局文件
  • 除了使用自定义的DrawerLayout和LinearLayout,其他和DrawerLayout使用完全一样,其中自定义DrawerLayout在添加View的时候,对我们的这个LinearLayout进行了一层包裹
  • 核心思路看addView方法,之后我们利用包裹一层RelativeLayout来把子控件的摆放和背景的特效分开,子控件摆放交给LinearLayout,背景的特效我们自定义View实现
  • 下面是背景特效的View,利用贝塞尔曲线
  • 自定义LinearLayout,根据手指的y坐标,对每个item设置不同的偏移量
  • 项目地址:https://gitee.com/aruba/DrawerLayoutApplication.git
领券
问题归档专栏文章快讯文章归档关键词归档开发者手册归档开发者手册 Section 归档