前言
如今安卓开发用抽屉的APP基本上很少见了,并不常用,无聊突然翻到以前自己写的自定义抽屉,特此来跟大家详解一下
介绍
实现原理:自定义继承自分层布局,使用事件分发,根据手指滑动的方向和距离进行判断抽屉打开的方向和位置
使用
1. 系统提供的抽屉框架包的使用
public class MainActivity extends AppCompatActivity implements View.OnDragListener, DrawerLayout.DrawerListener, View.OnClickListener {
private DrawerLayout activity_main;//GOOGLE 框架包
private RelativeLayout rl_left;// 左边抽屉
private ListView lv_left;// ListView
private List<String> list = new ArrayList<>();
private Button button;//开启左边抽屉
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
initList();
initView();
}
private void initList() {
for (int i = 0; i < 30; i++) {
list.add("sasf" + i);
}
}
private void initView() {
activity_main = (DrawerLayout) findViewById(R.id.activity_main);
rl_left = (RelativeLayout) findViewById(R.id.rl_left);
lv_left = (ListView) findViewById(R.id.lv_left);
button = (Button) findViewById(R.id.button);
lv_left.setAdapter(new ArrayAdapter<String>(this,
android.R.layout.simple_list_item_1, list));//给左边菜单写入数据
activity_main.setScrimColor(Color.TRANSPARENT);
activity_main.setDrawerLockMode(DrawerLayout.LOCK_MODE_LOCKED_CLOSED, Gravity.LEFT);
activity_main.setDrawerListener(this);
button.setOnClickListener(this);
}
//当抽屉正在滑动的时候
@Override
public void onDrawerSlide(View drawerView, float slideOffset) {
//获取第一个主页面的布局
View mContext = activity_main.getChildAt(0);
//获取滑动的抽屉的布局
View draw = drawerView;
float scale = 1 - slideOffset;
// 意思就是 如果不滑动 scale 是1 , 没有发生变化,滑动后 会缩小
float suofang = 0.8f + scale * 0.2f;
//测量 抽屉的宽度
int drawWidth = draw.getMeasuredWidth();
//主页区域
ViewHelper.setScaleX(mContext, suofang);
ViewHelper.setScaleY(mContext, suofang);
//抽屉区域
ViewHelper.setScaleX(draw, 0.7f + slideOffset * 0.3f);
ViewHelper.setScaleY(draw, 0.7f + slideOffset * 0.3f);
ViewHelper.setAlpha(draw, 0.6f + slideOffset * 0.4f);
//如果两个抽屉的话 可以对每一个抽屉 设置一个TAG 然后判断
ViewHelper.setTranslationX(mContext, drawWidth * slideOffset);
}
@Override
public void onDrawerOpened(View drawerView) {
}
//当抽屉关闭的时候会调用
@Override
public void onDrawerClosed(View drawerView) {
activity_main.setDrawerLockMode(DrawerLayout.LOCK_MODE_LOCKED_CLOSED, Gravity.LEFT);
}
@Override
public void onDrawerStateChanged(int newState) {
}
@Override
public boolean onDrag(View v, DragEvent event) {
return false;
}
@Override
public void onClick(View v) {
activity_main.openDrawer(Gravity.LEFT);
activity_main.setDrawerLockMode(DrawerLayout.LOCK_MODE_UNLOCKED, Gravity.LEFT);
}
}
2. 自定义 继承 FrameLayout 布局
public class SildingView extends FrameLayout {
private boolean IsFirst=true;//第一次进入的标志
private boolean IsSping=false;//抽屉菜单打开关闭的标志----》默认情况下关闭的
private PointF pf=new PointF();//记录坐标
private PointF pf1=new PointF();
private LinearLayout mBootomlinear;//底层的布局
private LinearLayout mToplinear;//顶层的布局
private int maxWidth=0;//抽屉打开的最大宽度
public SildingView(Context context, AttributeSet attrs) {
super(context, attrs);
initView();
}
private void initView() {
//实列化顶部底部布局
mBootomlinear=new LinearLayout(getContext());
mToplinear=new LinearLayout(getContext());
//设置线性方向-垂直
mBootomlinear.setOrientation(LinearLayout.VERTICAL);
mToplinear.setOrientation(LinearLayout.VERTICAL);
//方便查看设置背景颜色
mBootomlinear.setBackgroundColor(Color.BLACK);
mToplinear.setBackgroundColor(Color.WHITE);
}
//重写onmesure方法,获取底部布局的最大宽度
@Override
protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
super.onMeasure(widthMeasureSpec, heightMeasureSpec);
//获取view的宽度----》设置底部的宽度
if (IsFirst) {
maxWidth=(int) (getMeasuredWidth()*0.7);
mBootomlinear.setLayoutParams(new FrameLayout.LayoutParams(maxWidth, FrameLayout.LayoutParams.MATCH_PARENT));
mToplinear.setLayoutParams(new FrameLayout.LayoutParams(FrameLayout.LayoutParams.MATCH_PARENT, FrameLayout.LayoutParams.MATCH_PARENT));
//将布局添加到自定义里面
addView(mBootomlinear);//先添加哪个就在最底层
addView(mToplinear);
}
IsFirst=false;
}
//设置底部linear布局的方法
public void setBootom(View v){
//给要再bootom里面添加的布局设置其在父容器所占位置的宽高属性
v.setLayoutParams(new LinearLayout.LayoutParams(LinearLayout.LayoutParams.MATCH_PARENT, LinearLayout.LayoutParams.MATCH_PARENT));
//将布局添加到底部linear中
mBootomlinear.addView(v);
}
public void setTop(View v){
v.setLayoutParams(new LinearLayout.LayoutParams(LinearLayout.LayoutParams.MATCH_PARENT, LinearLayout.LayoutParams.MATCH_PARENT));
mToplinear.addView(v);
}
//底层事件的处理:return :1.true :自己处理了,不往下发 2.return super.dispatchtouchenvent(ev),交给系统自己处理-----》往下发
@Override
public boolean dispatchTouchEvent(MotionEvent ev) {
if (ev.getAction()==MotionEvent.ACTION_DOWN) {
//记录手指的坐标
pf.x=ev.getX();
pf.y=ev.getY();
pf1.x=ev.getX();
pf1.y=ev.getY();
}else if (ev.getAction()==MotionEvent.ACTION_MOVE) {
//获取当前手指滑动后的坐标
int x=(int) ev.getX();
int y=(int) ev.getY();
//计算手指滑动后的坐标距离: disX disY
int disX=(int) (x-pf.x);//x轴移动的距离
int disY=(int) (y-pf.y);//y轴移动的距离
//根据正余玹定理来判断, 水平滑动或者是垂直滑动
if (Math.abs(disX)/2-Math.abs(disY)>0) {
//不做处理
}else {//垂直状态--》抽屉应该关闭
//TODO 通过抽屉的开关来判断上层可否移动
if (!IsSping) {
return super.dispatchTouchEvent(ev);
}else {
return true;
}
}
//设置一个边界值:防止手指按下出现抽屉抖动的情况
if (Math.abs(disX)<10) {
return super.dispatchTouchEvent(ev);
}
//根据手指滑动的x轴移动的距离的正负,判断抽屉打开的方向
if (disX>0) {//
FrameLayout.LayoutParams lp=(LayoutParams) mToplinear.getLayoutParams();
//判断左边距超过最大边距。将最大边距设置给滑动 距离
if (lp.leftMargin>=maxWidth) {
disX=maxWidth;
IsSping=true;//抽屉开启
}
lp.leftMargin=disX;//将移动的距离设置给左边距
lp.rightMargin=-disX;
mToplinear.setLayoutParams(lp);//将属性设置给顶部布局
}else if (disX<0) {
//获取到顶部布局的属性lp
FrameLayout.LayoutParams lp=(LayoutParams) mToplinear.getLayoutParams();
if (lp.leftMargin<=0) {
disX=0;
IsSping=false;
}
lp.leftMargin=lp.leftMargin-Math.abs(disX);
lp.rightMargin=-lp.leftMargin;
mToplinear.setLayoutParams(lp);
pf.x=x;//将移动后的坐标赋值给初始坐标,解决再次移动的问题
}
requestLayout();//刷新界面
return true;
}else if (ev.getAction()==MotionEvent.ACTION_UP) {
//区分是点击还是滑动
int disX=(int) Math.abs(ev.getX()-pf1.x);
if (disX>10) {
//以底部linear的宽度的一半为分割线,超过分割线,手指抬起,手指抬起,抽屉自动打开或关闭
FrameLayout.LayoutParams lp=(LayoutParams) mToplinear.getLayoutParams();
if (lp.leftMargin>maxWidth/2) {//抽屉自动打开
lp.leftMargin=maxWidth;
lp.rightMargin=-maxWidth;
IsSping=true;
}else {
//抽屉关闭
lp.leftMargin=0;
lp.rightMargin=0;
IsSping=false;
}
mToplinear.setLayoutParams(lp);
requestLayout();
return true;//自己处理
}
}
return super.dispatchTouchEvent(ev);
}
}
3. 系统提供的横向 滑动的 布局 HorizontalScrollView
public class MySlidingMenu extends HorizontalScrollView {
// 屏幕宽度 单位:px
private int ScreenWidth;
// 内容区域宽度
private int contentWidth;
// 菜单宽度
private int menuWidth;
// 菜单一半的宽度
private int halfmenuWidth;
// 菜单的右边距
private int rightPadding;
private boolean isMesure;
// 是否打开菜单
private boolean isOpen;
public MySlidingMenu(Context context, AttributeSet attrs, int defStyle) {
super(context, attrs, defStyle);
//用来添加view
WindowManager wm = (WindowManager) context
.getSystemService(Context.WINDOW_SERVICE);
//获取手机分辨率
DisplayMetrics outMetrics = new DisplayMetrics();
//获取屏幕的大小
wm.getDefaultDisplay().getMetrics(outMetrics);
//widthPixels是宽度方向上的像素点的个数。
ScreenWidth = outMetrics.widthPixels;
//自定义属性
TypedArray ta = context.getTheme().obtainStyledAttributes(attrs,
R.styleable.MySlidingMenu, defStyle, 0);
int count =ta.getIndexCount();
for(int i=0;i<count;i++){
int attr=ta.getIndex(i);
Log.e("attr", " "+attr);
switch(attr){
case R.styleable.MySlidingMenu_rightPadding:
//把dp转化为px
rightPadding=ta.getDimensionPixelOffset(attr,
(int) TypedValue.applyDimension(
TypedValue.COMPLEX_UNIT_DIP, 200,
getResources().getDisplayMetrics()));
break;
}
}
}
public MySlidingMenu(Context context) {
this(context, null, 0);
}
public MySlidingMenu(Context context, AttributeSet attrs) {
this(context, attrs,0);
// WindowManager wm = (WindowManager) context
// .getSystemService(Context.WINDOW_SERVICE);
//
// DisplayMetrics outMetrics = new DisplayMetrics();
//
// wm.getDefaultDisplay().getMetrics(outMetrics);
// ScreenWidth = outMetrics.widthPixels;
}
@Override
protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
super.onMeasure(widthMeasureSpec, heightMeasureSpec);
if (!isMesure) {
// 避免 Measure方法多次调用
isMesure = true;
LinearLayout wrapper = (LinearLayout) getChildAt(0);
ViewGroup menu = (ViewGroup) wrapper.getChildAt(0);
ViewGroup content = (ViewGroup) wrapper.getChildAt(1);
// rightPadding = (int) TypedValue.applyDimension(
// TypedValue.COMPLEX_UNIT_DIP, 100, content.getResources()
// .getDisplayMetrics());
//屏幕宽度-菜单的右边距 = 菜单的宽度
menuWidth = ScreenWidth - rightPadding;
halfmenuWidth = menuWidth / 2;
//获取菜单的布局属性获得菜单的宽度
menu.getLayoutParams().width = menuWidth;
//主布局的宽度 = 屏幕的宽度
content.getLayoutParams().width = ScreenWidth;
}
}
@Override
protected void onLayout(boolean changed, int l, int t, int r, int b) {
super.onLayout(changed, l, t, r, b);
if (changed) {
//(让ScrollView滚动到(menuWidth, 0)的位置,也就是刚好显示主视图)不带效果的滑动
this.scrollTo(menuWidth, 0);
}
}
@Override
public boolean onTouchEvent(MotionEvent ev) {
switch (ev.getAction()) {
case MotionEvent.ACTION_UP:
//就是当前view的左上角相对于母视图的左上角的X轴偏移量。
int scrollX = getScrollX();
if (scrollX > halfmenuWidth) {
this.smoothScrollTo(menuWidth, 0);
} else {
this.smoothScrollTo(0, 0);
}
return true;
}
return super.onTouchEvent(ev);
}
public void openDraw() {
if (isOpen) {
return;
}
//让ScrollView滚动到(0,0)的位置
this.smoothScrollTo(0,0);
isOpen=true;
}
public void close() {
if (isOpen) {
//让ScrollView滚动到(menuWidth,0)的位置
//带效果的滑动
this.smoothScrollTo(menuWidth,0);
isOpen=false;
}
}
/*
可以设置一个按钮控制抽屉的开关
*/
public void toggle(){
if(isOpen){
close();
}else{
openDraw();
}
}
}
自定义属性
<?xml version="1.0" encoding="utf-8"?>
<resources>
<attr
name="rightPadding" format="dimension"
/>
<declare-styleable name="MySlidingMenu">
<attr name="rightPadding" />
</declare-styleable>
</resources>
好了,了解一下,现在抽屉已经使用不多了