自定义侧滑效果.gif
上次说到自定义属性在系统控件上的应用,今天继续利用这个思想,基于DrawerLayout打造自己的侧滑效果
首先看下我们的布局文件
除了使用自定义的DrawerLayout和LinearLayout,其他和DrawerLayout使用完全一样,其中自定义DrawerLayout在添加View的时候,对我们的这个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);
}
}
@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);
}
}
}
@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实现
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));
}
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,利用贝塞尔曲线
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();
}
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设置不同的偏移量
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;
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);
}
}
}
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