侧滑菜单功能非常常见,借鉴学习了之后,自己总结记录一下,下面实现一种最简单的侧滑菜单,后面再修改代码实现不同的侧滑菜单效果
首先是第一种效果
第一种效果是继承ViewGroup,需要我们自己来测量、滑动处理等。
一、首先讲解一下思路:
1、继承GroupView重写构造方法
a、我们需要重写三个构造方法
b、在构造方法中初始化宽高值等
2、重写onMeasure方法
a、在onMeasure方法中调用measureChild来测量子view,即我们的menu跟content两个view。
b、调用setMeasureDimension来测量自己的大小。
3、重写onLayout方法。
a、调用menu跟content两个view的layout方法来确定它们的位置。
4、重写onTouchEvent事件
a、记录手指按下时的x跟y值
b、通过手指移动时的x值减去记录好的x值进行判断当前是向左还是向右移动进行页面menu的打开或者关闭
c、当手指松开时,我们判断menu有没有打开超过它自身的一半宽度进行自动打开或者关闭
5、重写onInterceptTouchEvent事件
a、由于我们的主页面中可能有listview或者scrollview那些可以上下滚动的view存在,可能会造成我们的事件冲突,所以需要重写onInterceptTouchEvent事件来区分当前用户是向上(下)还是向右(左)滑动。
6、实例化Scroller类实现滚动打开关闭
a、由于我们需要加入一个按钮,点击后打开或者关闭menu,所以我们需要这个滚动类
二、我们看看我们页面上的布局是什么样的
如上图片:我们布局应该是content在我们的屏幕中间,而menu超出屏幕在左边,当我们手指往右边滑的时候,menu跟随手指滑动出来
而当我们完全打开了menu之后,页面应该是显示成这样
三、首先是我们的自定义view类
public class SlidingMenu extends ViewGroup {
private Scroller mScroller;
private int mMenuRightPadding;
private int heightPixels;
private int widthPixels;
private int mMenuWidth;
private View mMenu;
private View mContent;
private int mLastY;
private int mLastX;
private boolean isOpen;
private int mLastYIntercept;
private int mLastXIntercept;
public SlidingMenu(Context context) {
this(context,null,0);
}
public SlidingMenu(Context context, AttributeSet attrs) {
this(context,attrs,0);
}
public SlidingMenu(Context context, AttributeSet attrs, int defStyleAttr) {
super(context, attrs, defStyleAttr);
DisplayMetrics metrics=new DisplayMetrics();
WindowManager windowManager= (WindowManager) context.getSystemService(Context.WINDOW_SERVICE);
windowManager.getDefaultDisplay().getMetrics(metrics);
//获取屏幕的宽高
widthPixels = metrics.widthPixels;
heightPixels = metrics.heightPixels;
//初始化mMenuRightPadding,作为侧边view到屏幕右边的距离,converToDp方法将100转换为100dp
mMenuRightPadding=converToDp(context,100);
}
@Override
protected void onLayout(boolean changed, int l, int t, int r, int b) {
//侧滑view要放置的位置,左上右下分别是menu的负宽度,因为要超出屏幕的左边,而上边跟右边都是0,下边就是屏幕的高度值,因为要占满全屏,如果不需要,可以改动
mMenu.layout(-mMenuWidth,0,0,heightPixels);
//content区域位置,屏幕的中间
mContent.layout(0,0,widthPixels,heightPixels);
}
@Override
protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
//获取SlidingMenu的第一个子孩子控件,即我们的侧边栏view
mMenu = this.getChildAt(0);
//获取SlidingMenu的第二个子孩子控件,即我们的内容区域
mContent=this.getChildAt(1);
//计算menu要使用的宽度为屏幕宽度减去menu要距离屏幕右侧的距离
mMenuWidth=widthPixels - mMenuRightPadding;
//设置menu的宽度
mMenu.getLayoutParams().width = mMenuWidth;
//设置content的宽度
mContent.getLayoutParams().width=widthPixels;
//测量mMenu
this.measureChild(mMenu, widthMeasureSpec, heightMeasureSpec);
//测量content
this.measureChild(mContent, widthMeasureSpec, heightMeasureSpec);
//测量自己,自己的宽度为侧边栏已经内容区域的宽度总和,高度为全屏
this.setMeasuredDimension(widthPixels+mMenuWidth,heightPixels);
}
/**
* 转换px为pd
* @param context 上下文
* @param px 要转换的px数值
* @return 返回转换好的结果值
*/
private int converToDp(Context context,int px){
int result=0;
result= (int) TypedValue.applyDimension(TypedValue.COMPLEX_UNIT_DIP,px,context.getResources().getDisplayMetrics());
return result;
}
}
按照上面所说的我们的布局的样子,我们开始书写我们的xml
四、首先是view_content.xml,给了一个listview
<?xml version="1.0" encoding="utf-8"?>
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:orientation="vertical"
android:background="#000000">
<ListView
android:id="@+id/content_lv"
android:layout_width="match_parent"
android:layout_height="match_parent">
</ListView>
</LinearLayout>
五、 view_menu.xml,也是一样的布局
<?xml version="1.0" encoding="utf-8"?>
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
android:layout_width="match_parent"
android:layout_height="match_parent">
<ListView
android:id="@+id/menu_lv"
android:layout_width="match_parent"
android:layout_height="match_parent">
</ListView>
</LinearLayout>
六、我们还需要listview的item布局,简单起见,view_content跟view_menu里面的listview我们都用同一个view_item.xml布局
<?xml version="1.0" encoding="utf-8"?>
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:orientation="horizontal">
<ImageView
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:src="@mipmap/ic_launcher"
android:layout_gravity="center_vertical"
/>
<TextView
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_gravity="center_vertical"
android:text="内容"/>
</LinearLayout>
七、最后是我们的main.xml布局
<RelativeLayout xmlns:android="http://schemas.android.com/apk/res/android"
android:layout_width="match_parent"
android:layout_height="match_parent"
>
<com.mjz.menudemo.view.SlidingMenu
android:id="@+id/slidingmenu"
android:layout_width="wrap_content"
android:layout_height="match_parent"
>
<include
android:id="@+id/menu"
layout="@layout/view_menu"
/>
<include
android:id="@+id/content"
layout="@layout/view_content"
/>
</com.mjz.menudemo.view.SlidingMenu>
</RelativeLayout>
八、注意布局中的menu跟content的顺序要对,因为我们在自定义view——SlidingMenu.java中有这么一段代码,代码中查找子view的位置是用下标的
@Override
protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
//获取SlidingMenu的第一个子孩子控件,即我们的侧边栏view
mMenu = this.getChildAt(0);
//获取SlidingMenu的第二个子孩子控件,即我们的内容区域
mContent=this.getChildAt(1);
}
九、此时我们的xml布局写好了,大概效果已经出来了,现在我们要让menu能打开以及关闭
我们需要让view跟着我们的手指滑动而滑动,我们可以使用View的scrollTo和scrollBy方法,这两个方法的区别是scrollTo是直接将view移动到指定的位置,scrollBy是相对于当前的位置移动一个偏移量,所以我们应该重写onTouchEvent方法,用来计算出当前手指的一个偏移量,然后使用scrollBy方法一点一点的移动,就形成了一个可以跟随手指移动的view的动画效果了
十、现在我们看一下各大坐标的值
当menu滚动到一半的时候,情况是这样的
而menu全部拉出来之后,情况如下
十一、弄清楚这些情况之后,我们开始重写onTouchEvent方法
<span style="font-family:microsoft yahei;color:#555555;"><span style="font-size: 14px; line-height: 35px; background-color: rgb(240, 240, 240);">@Override
public boolean onTouchEvent(MotionEvent event) {
int action=event.getAction();
switch (action){
//手指按下
case MotionEvent.ACTION_DOWN:
//记录按下时的x跟y值
mLastX= (int) event.getX();
mLastY= (int) event.getY();
break;
//手指移动
case MotionEvent.ACTION_MOVE:
//时刻捕捉移动的手指位置的x跟y值
int currentX= (int) event.getX();
int currentY= (int) event.getY();
//计算出当前的x值偏移量
int offsetX=currentX-mLastX;
if(offsetX>0){//大于0代表着当前手指是往右滑的
//首先要判断是不是滑到了最右边(menu已经全部显示出来了),如果全部显示了,再继续往右边滑,会造成超出了menu
if(getScrollX()-offsetX<=-mMenuWidth){
scrollTo(-mMenuWidth,0);
}else{
scrollBy(-offsetX,0);
}
}else{
//否则是往左边滑动
if(getScrollX()+Math.abs(offsetX)>=0){
scrollTo(0,0);
}else{
scrollBy(-offsetX,0);
}
}
//移动过程中,修改记录每一次移动到的x跟y的位置
mLastX = currentX;
mLastY = currentY;
break;
//如果手指离开屏幕
case MotionEvent.ACTION_UP:
if(getScrollX()<-mMenuWidth/2){
//打开menu
//使用startScroll方法,第一个参数是X的起始坐标,第二个参数是Y的起始坐标,第三个参数是X方向偏移量(即要移动多少),第四个参数是Y方向偏移量,第五个参数是动画持续时间
mScroller.startScroll(getScrollX(),0,-mMenuWidth-getScrollX(),0,200);
isOpen=true;
invalidate();
}else{
//如果当前滚动到的X位置是大于侧边栏的宽度的一半的,说明是没达到打开menu的要求
mScroller.startScroll(getScrollX(),0,-getScrollX(),0,200);
isOpen=false;
invalidate();
}
break;
}
return true;
}
</span></span>
MotionEvent.ACTION_UP中我们对menu进行了是否打开了二分之一的判断,如果打开了二分之一,我们就自动打开,否则就自动关闭,这个时候,我们需要用到Scroller类。
十二、我们在构造方法中初始化一个Scroller
public SlidingMenu(Context context, AttributeSet attrs, int defStyleAttr) {
super(context, attrs, defStyleAttr);
****中间的代码直接省略****
mScroller = new Scroller(context);
}
然后重写computeScroll方法,这个方法是保证Scroller自动滑动的必须方法
@Override
public void computeScroll() {
//动画是否已经结束
if(mScroller.computeScrollOffset()){
scrollTo(mScroller.getCurrX(),mScroller.getCurrY());
invalidate();
}
}
此时如果你的listview已经加入adapter且有数据了,你会发现,还是不能侧滑,原因是因为我们的SlidingMenu默认是不拦截事件的,那么事件会传递给他的子View去执行,也就是说传递给了Content的ListView去执行了,所以listview是可以滑动的,所以我们需要重写onInterceptTouchEvent方法,当向上或者向下滑动的时候,我们将滑动的事件传递给listview,向左向右则SlidingMenu自己处理
十三、重写onInterceptTouchEvent方法
@Override
public boolean onInterceptTouchEvent(MotionEvent ev) {
boolean intercept=false;
int x= (int) ev.getX();
int y= (int) ev.getY();
switch (ev.getAction()){
case MotionEvent.ACTION_DOWN:
intercept=false;
break;
case MotionEvent.ACTION_MOVE:
int deltaX=(int) ev.getX()-mLastXIntercept;
int deltaY=(int) ev.getY()-mLastYIntercept;
if(Math.abs(deltaX)>Math.abs(deltaY)){//左右活动
intercept=true;
}else{//上下滑动
intercept=false;
}
break;
case MotionEvent.ACTION_UP:
intercept=false;
break;
}
mLastX=mLastXIntercept=x;
mLastY=mLastYIntercept=y;
return intercept;
}
到此,我们的功能基本实现了,侧滑跟listview的上下滑动也不冲突了,以上是简单的侧滑菜单的实现