自定义侧滑效果.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);
}
}
网友评论