看下整体实现效果,侧边滑出一个菜单选项。
完整代码详见
首先,我们需要实现从右边滑出菜单选项的背景色,利用二阶贝塞尔曲线即可实现:
import android.content.Context;
import android.graphics.Canvas;
import android.graphics.Color;
import android.graphics.Paint;
import android.graphics.Path;
import android.graphics.drawable.ColorDrawable;
import android.graphics.drawable.Drawable;
import android.util.AttributeSet;
import android.view.View;
import androidx.annotation.Nullable;
public class MenuBgView extends View {
Paint paint;
Path path;
public MenuBgView(Context context) {
this(context, null);
}
public MenuBgView(Context context, @Nullable AttributeSet attrs) {
this(context, attrs, 0);
}
public MenuBgView(Context context, @Nullable AttributeSet attrs, int defStyleAttr) {
super(context, attrs, defStyleAttr);
init();
}
private void init() {
paint = new Paint();
paint.setAntiAlias(true);
paint.setStyle(Paint.Style.FILL);
paint.setColor(Color.BLUE);
path = new Path();
}
public void setTouchY(float y, float percent) {
path.reset();
float width = getWidth() * percent;
float offsetY = getHeight() / 9;
float beginX = 0;
float beginY = -offsetY;
float endX = 0;
float endY = getHeight() + offsetY;
float controllX = width * 3 / 2;
float controllY = y;
path.lineTo(beginX, beginY);
path.quadTo(controllX, controllY, endX, endY);
path.close();
invalidate();
}
@Override
protected void onDraw(Canvas canvas) {
super.onDraw(canvas);
canvas.drawPath(path, paint);
}
public void setColor(Drawable color) {
if (color instanceof ColorDrawable) {
ColorDrawable colorDrawable= (ColorDrawable) color;
paint.setColor(colorDrawable.getColor());
}
}
}
在MainActivity中使用:
import androidx.appcompat.app.AppCompatActivity;
import android.os.Bundle;
import android.view.MotionEvent;
public class MainActivity extends AppCompatActivity {
MenuBgView menuBgView;
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
// setContentView(R.layout.activity_main);
menuBgView = new MenuBgView(this);
setContentView(menuBgView);
}
@Override
public boolean onTouchEvent(MotionEvent event) {
menuBgView.setTouchY(event.getY(),0.8f);
return super.onTouchEvent(event);
}
}
完成后效果如图,这个就是我们菜单栏的背景view。
下面完成菜单view里面的文字内容的view,这里叫MenuContentLayout:
import android.content.Context;
import android.content.res.TypedArray;
import android.util.AttributeSet;
import android.view.View;
import android.widget.LinearLayout;
import androidx.annotation.Nullable;
public class MenuContentLayout extends LinearLayout {
private float maxTranslationX;
public MenuContentLayout(Context context) {
this(context, null);
}
public MenuContentLayout(Context context, @Nullable AttributeSet attrs) {
this(context, attrs, 0);
}
public MenuContentLayout(Context context, @Nullable AttributeSet attrs, int defStyleAttr) {
super(context, attrs, defStyleAttr);
init(context, attrs);
}
private void init(Context context, AttributeSet attrs) {
setOrientation(VERTICAL);
if (attrs != null) {
TypedArray array = getContext().obtainStyledAttributes(attrs, R.styleable.SideBar);
maxTranslationX = array.getDimension(R.styleable.SideBar_maxTranslationX, 0);
array.recycle();
}
}
public void setTouchY(float y, float slideOffset) {
for (int i = 0; i < getChildCount(); i++) {
View child = getChildAt(i);
apply(child, y, slideOffset);
}
}
private void apply(View child, float y, float slideOffset) {
float translationX = 0;
int centerY = child.getTop() + child.getHeight() / 2;
float distance = Math.abs(y - centerY);
float scale = distance / getHeight() * 3;
translationX = maxTranslationX - scale * maxTranslationX;
child.setTranslationX(translationX);
}
}
上面这个view也很简单,就是遍历整个MenuContentLayout里面的子view,拿出来一个一个动态的设置他们的位置,这里主要是如何根据手指的上下移动,来确定和哪个内容子view最近,从而将该子view重新设置它的横轴位置,达到动态的左右移动。
下面这个MenuPutLayout就是将上面两个自定义view进行整合的layout,负责传递onTouchEvent事件,以及设置两个子view:
import android.content.Context;
import android.graphics.Color;
import android.util.AttributeSet;
import android.widget.RelativeLayout;
public class MenuPutLayout extends RelativeLayout {
MenuContentLayout contentLayout;
MenuBgView bgView;
public MenuPutLayout(MenuContentLayout contentLayout) {
this(contentLayout.getContext());
init(contentLayout);
}
public MenuPutLayout(Context context) {
this(context, null);
}
public MenuPutLayout(Context context, AttributeSet attrs) {
this(context, attrs, 0);
}
public MenuPutLayout(Context context, AttributeSet attrs, int defStyleAttr) {
super(context, attrs, defStyleAttr);
}
private void init(MenuContentLayout contentLayout) {
this.contentLayout = contentLayout;
//把content的 宽高转移到外面RelatiLayout
setLayoutParams(contentLayout.getLayoutParams());
//背景先添加进去
bgView = new MenuBgView(getContext());
bgView.setColor(contentLayout.getBackground());
addView(bgView, 0, new LayoutParams(LayoutParams.MATCH_PARENT, LayoutParams.MATCH_PARENT));
//把contentView 的背景颜色取出来 设置给 bgView 把contentView弄成透明
contentLayout.setBackgroundColor(Color.TRANSPARENT);
addView(contentLayout, new LayoutParams(LayoutParams.MATCH_PARENT, LayoutParams.MATCH_PARENT));
}
/**
* 传递偏移Y
* @param y
* @param slideOffset
*/
public void setTouchY(float y, float slideOffset) {
bgView.setTouchY(y,slideOffset);
contentLayout.setTouchY(y, slideOffset);
}
}
最后就是MenuDrawerLayout,这里利用DrawerLayout的滑动回调的参数,往MenuPutLayout中传递参数:
import android.content.Context;
import android.util.AttributeSet;
import android.view.MotionEvent;
import android.view.View;
import androidx.annotation.NonNull;
import androidx.annotation.Nullable;
import androidx.core.view.GravityCompat;
import androidx.drawerlayout.widget.DrawerLayout;
public class MenuDrawerLayout extends DrawerLayout implements DrawerLayout.DrawerListener {
private MenuContentLayout menuContentLayout;
private View contentView;
private MenuPutLayout menuPutLayout;
private float y;
private float slideOffset;
public MenuDrawerLayout(@NonNull Context context) {
super(context);
}
public MenuDrawerLayout(@NonNull Context context, @Nullable AttributeSet attrs) {
super(context, attrs);
}
public MenuDrawerLayout(@NonNull Context context, @Nullable AttributeSet attrs, int defStyle) {
super(context, attrs, defStyle);
}
@Override
protected void onFinishInflate() {
super.onFinishInflate();
init();
}
private void init() {
for (int i = 0; i < getChildCount(); i++) {
View view = getChildAt(i);
if (view instanceof MenuContentLayout) {
menuContentLayout = (MenuContentLayout) view;
} else {
contentView = view;
}
}
removeView(menuContentLayout);
menuPutLayout = new MenuPutLayout(menuContentLayout);
addView(menuPutLayout);
addDrawerListener(this);
}
@Override
public boolean dispatchTouchEvent(MotionEvent ev) {
y = ev.getY();
//没有打开之前 不拦截 打开之后拦不拦截 大于1 后 内容区域不再进行偏移
// if (slideOffset < 0.8) {
// return super.dispatchTouchEvent(ev);
// } else {
// //等于 1
// menuPutLayout.setTouchY(y, slideOffset);
// }
return super.dispatchTouchEvent(ev);
}
@Override
public void onDrawerSlide(@NonNull View drawerView, float slideOffset) {
this.slideOffset = slideOffset;
menuPutLayout.setTouchY(y, slideOffset);
//针对内容区域进行破偏移
float contentViewOffset = drawerView.getWidth() * slideOffset / 2;
contentView.setTranslationX(contentViewOffset);
}
@Override
public void onDrawerOpened(@NonNull View drawerView) {
setDrawerLockMode(DrawerLayout.LOCK_MODE_LOCKED_OPEN, GravityCompat.END);
}
@Override
public void onDrawerClosed(@NonNull View drawerView) {
}
@Override
public void onDrawerStateChanged(int newState) {
}
}
整个项目的完整代码详见:https://github.com/buder-cp/CustomView/tree/master/buder_DN_view/buderdn18