package com.tenghu.sideslipmenu.view;
import android.content.Context;
import android.os.AsyncTask;
import android.util.AttributeSet;
import android.util.DisplayMetrics;
import android.view.MotionEvent;
import android.view.VelocityTracker;
import android.view.View;
import android.view.WindowManager;
import android.widget.LinearLayout;
/**
* Created by Arvin_Li on 2014/11/18.
*/
public class SideslipMenuView extends LinearLayout {
//滚动显示和隐藏menu时,手指滑动需要达到的速度
private static final int SNAP_VELOCITY = 200;
private View mMenu;//菜单布局
private View mContent;//主内容布局
private int mScreenWidth;//屏幕宽度
private int mMenuWidth;//菜单宽度
//menu最多可以滑动到的左边缘,值由menu布局的宽度来定
private int leftEdge;
//menu最多可以滑到的有边缘,值恒为0
private int rightEdge = 0;
//menu完全显示时,留给content的宽度值
private int toRightPaddingWidth = 50;
//menu布局的参数,通过此参数来更改leftMargin的值
private LinearLayout.LayoutParams menuParams;
//记录手指按下的横坐标
private float xDown;
//记录手指抬起的横坐标
private float xUp;
//记录手指移动的横坐标
private float xMove;
//当前menu是显示还是隐藏,只有menu完全显示和隐藏才会改变该值,滑动时不会改变
private boolean isMenuVisible;
//用于计算手指滑动的速度
private VelocityTracker mVelocityTracker;
private boolean once;
public SideslipMenuView(Context context) {
super(context);
init(context);
}
public SideslipMenuView(Context context, AttributeSet attrs) {
super(context, attrs);
init(context);
}
/**
* 初始化
*/
private void init(Context context) {
//获取WindowManager对象
WindowManager windowManager = (WindowManager) context.getSystemService(Context.WINDOW_SERVICE);
DisplayMetrics displayMetrics = new DisplayMetrics();
windowManager.getDefaultDisplay().getMetrics(displayMetrics);
mScreenWidth = displayMetrics.widthPixels;//获取屏幕宽度
}
@Override
protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
super.onMeasure(widthMeasureSpec, heightMeasureSpec);
if (!once) {
mMenu = getChildAt(0);//获取菜单布局
mContent = getChildAt(1);//获取内容布局
menuParams = (LayoutParams) mMenu.getLayoutParams();//获取菜单参数
mMenuWidth = menuParams.width = mScreenWidth - toRightPaddingWidth;//设置菜单宽度
//左边缘的值赋值为menu宽度的负数
leftEdge = -menuParams.width;
//menu的leftMargin设置为左边缘的值,默认菜单不可见
menuParams.leftMargin = leftEdge;
mContent.getLayoutParams().width = mScreenWidth;//设置内容宽度
once = true;
}
}
@Override
public boolean onTouchEvent(MotionEvent event) {
createVelocityTracker(event);//创建VelocityTracker对象
switch (event.getAction()) {
//手指按下
case MotionEvent.ACTION_DOWN:
xDown = event.getRawX();//记录横坐标
break;
//手指移动
case MotionEvent.ACTION_MOVE:
//手指移动时,对比按下的横坐标,计算出移动的距离来调整menu的leftMargin值,从而显示和隐藏menu
xMove = event.getRawX();
//计算获取到距离
int distanceX = (int) (xMove - xDown);
if (isMenuVisible) {
menuParams.leftMargin = distanceX;
} else {
menuParams.leftMargin = leftEdge + distanceX;
}
//如果菜单的左边界小于了可以滑动的左边界
if (menuParams.leftMargin <= leftEdge) {
//将可以滑动的左边界赋值给菜单左边界
menuParams.leftMargin = leftEdge;
} else if (menuParams.leftMargin >= rightEdge) {
menuParams.leftMargin = rightEdge;
}
mMenu.setLayoutParams(menuParams);//设置菜单参数
break;
//手指抬起
case MotionEvent.ACTION_UP:
//手指抬起时,进行判断当前手势的意图,从而决定是滚动到menu界面,还是滚动到content界面
xUp = event.getRawX();
if (wantToShowMenu()) {
if (shouldScrollToMenu()) {
scrollToMenu();
} else {
scrollToContent();
}
} else if (wantToShowContent()) {
if (shouldScrollToContent()) {
scrollToContent();
} else {
scrollToMenu();
}
}
//手指抬起是回收VelocityTracker对象
recycleVelocityTracker();
break;
}
return true;
}
/**
* 创建VelocityTracker对象,并将触摸content界面的滑动时间加入到VelocityTracker中
*
* @param event
*/
private void createVelocityTracker(MotionEvent event) {
if (null == mVelocityTracker) {
mVelocityTracker = VelocityTracker.obtain();//初始化VelocityTracker
}
mVelocityTracker.addMovement(event);//添加界面滑动事件
}
/**
* 判断当前手势的意图是不是想显示content,如果手指移动的距离是负数,且当前menu是可见的,则认为当前手势是想要显示content。
*
* @return
*/
private boolean wantToShowContent() {
return xUp - xDown < 0 && isMenuVisible;
}
/**
* 判断当前手势的意图是不是想显示menu,如果手指移动的距离是正数,且当前menu是不可见,则认为当前手势是显示menu
*
* @return
*/
private boolean wantToShowMenu() {
return xUp - xDown > 0 && !isMenuVisible;
}
/**
* 判断是否应该滚动将menu展示出来,如果手指移动距离大于屏幕的1/2,或者手指移动速度大于SNAP_VELOCITY,就认为应该滚动将menu展示出来。
*
* @return 如果应该滚动将menu展示出来返回true,否则返回false。
*/
private boolean shouldScrollToMenu() {
return xUp - xDown > mMenuWidth / 2 || getScrollVelocity() > SNAP_VELOCITY;
}
/**
* 判断是否应该滚动将content展示出来,如果手指移动距离加上menuPadding大于屏幕的1/2,或者手指移动速度大于SNAP_VELOCITY, 就认为应该滚动将content展示出来。
*
* @return 如果应该滚动将content展示出来返回true,否则返回false。
*/
private boolean shouldScrollToContent() {
//这里菜单状态为显示,如果要菜单隐藏,那么手势是从右到左滑动,这里手指按下的横坐标就会比抬起时的横坐标小
return xDown - xUp + toRightPaddingWidth > mMenuWidth / 2 || getScrollVelocity() > SNAP_VELOCITY;
}
/**
* 获取手指在content上滑动的速度
*
* @return 滑动速度,以每秒钟移动了多少像素为单位
*/
private int getScrollVelocity() {
mVelocityTracker.computeCurrentVelocity(1000);
int velocity = (int) mVelocityTracker.getXVelocity();//获取x方向的速度值
return Math.abs(velocity);
}
/**
* 回收VelocityTracker对象
*/
private void recycleVelocityTracker() {
if (null != mVelocityTracker) {
mVelocityTracker.recycle();
mVelocityTracker = null;
}
}
/**
* 将屏幕滚动到menu界面,设置滚动速度为30
*/
private void scrollToMenu() {
isMenuVisible = true;
new ScrollTask().execute(30);
}
/**
* 将屏幕滚动到content界面,设置滚动速度为-30
*/
private void scrollToContent() {
isMenuVisible = false;
new ScrollTask().execute(-30);
}
/**
* 创建滚动任务类
*/
class ScrollTask extends AsyncTask<Integer, Integer, Integer> {
@Override
protected Integer doInBackground(Integer... speed) {
int leftMargin = menuParams.leftMargin;
//根据传入的速度来滚动界面,当滚动到达左边界或右边界时跳出循环
while (true) {
leftMargin = leftMargin + speed[0];
if (leftMargin > rightEdge) {
leftMargin = rightEdge;
break;
}
if (leftMargin < leftEdge) {
leftMargin = leftEdge;
break;
}
//更新任务进度,会把值传入到onProgressUpdate()方法中进行UI的更新
publishProgress(leftMargin);
//为了要有滚动效果产生,每次循环使线程睡眠20毫秒
sleep(20);
}
return leftMargin;
}
/**
* 这里的Intege参数对应AsyncTask中的第二个参数
* 在doInBackground方法当中,,每次调用publishProgress方法都会触发onProgressUpdate执行
* onProgressUpdate是在UI线程中执行,所有可以对UI空间进行操作
*
* @param leftMargin
*/
@Override
protected void onProgressUpdate(Integer... leftMargin) {
menuParams.leftMargin = leftMargin[0];
mMenu.setLayoutParams(menuParams);
}
/**
* 执行异步结束,接收doInBackground()方法的返回值,接收到的返回值对应AsyncTask<Integer, Integer, Integer> 第3个参数
*
* @param leftMargin
*/
@Override
protected void onPostExecute(Integer leftMargin) {
menuParams.leftMargin = leftMargin;
mMenu.setLayoutParams(menuParams);
}
}
/**
* 是当前线程睡眠指定的毫秒数
*
* @param millis
*/
private void sleep(long millis) {
try {
Thread.sleep(millis);
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}
以上就是整个自定义侧滑菜单控件的代码,在布局文件直接使用即可如下:
<?xml version="1.0" encoding="utf-8"?>
<com.tenghu.sideslipmenu.view.SideslipMenuView xmlns:android="http://schemas.android.com/apk/res/android"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:background="@drawable/bg_01">
<include layout="@layout/left_menu" />
<LinearLayout
android:layout_width="match_parent"
android:layout_height="match_parent"
android:background="@drawable/bg_02"
android:orientation="vertical">
</LinearLayout>
</com.tenghu.sideslipmenu.view.SideslipMenuView>
其中include引用的就是一个菜单布局文件,如下:
<?xml version="1.0" encoding="utf-8"?>
<RelativeLayout xmlns:android="http://schemas.android.com/apk/res/android"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:gravity="center_vertical">
<LinearLayout
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_marginLeft="30dp"
android:orientation="vertical">
<RelativeLayout
android:layout_width="wrap_content"
android:layout_height="50dp"
android:layout_marginBottom="10dp">
<ImageView
android:id="@+id/iv_img_01"
android:layout_width="50dp"
android:layout_height="match_parent"
android:src="@drawable/app_01" />
<TextView
android:layout_width="match_parent"
android:layout_height="match_parent"
android:layout_marginLeft="10dp"
android:layout_toRightOf="@id/iv_img_01"
android:gravity="center_vertical"
android:text="第一个Item" />
</RelativeLayout>
<RelativeLayout
android:layout_width="wrap_content"
android:layout_height="50dp"
android:layout_marginBottom="10dp">
<ImageView
android:id="@+id/iv_img_02"
android:layout_width="50dp"
android:layout_height="match_parent"
android:src="@drawable/app_02" />
<TextView
android:layout_width="match_parent"
android:layout_height="match_parent"
android:layout_marginLeft="10dp"
android:layout_toRightOf="@id/iv_img_02"
android:gravity="center_vertical"
android:text="第二个Item" />
</RelativeLayout>
<RelativeLayout
android:layout_width="wrap_content"
android:layout_height="50dp"
android:layout_marginBottom="10dp">
<ImageView
android:id="@+id/iv_img_03"
android:layout_width="50dp"
android:layout_height="match_parent"
android:src="@drawable/app_03" />
<TextView
android:layout_width="match_parent"
android:layout_height="match_parent"
android:layout_marginLeft="10dp"
android:layout_toRightOf="@id/iv_img_03"
android:gravity="center_vertical"
android:text="第三个Item" />
</RelativeLayout>
<RelativeLayout
android:layout_width="wrap_content"
android:layout_height="50dp"
android:layout_marginBottom="10dp">
<ImageView
android:id="@+id/iv_img_04"
android:layout_width="50dp"
android:layout_height="match_parent"
android:src="@drawable/app_04" />
<TextView
android:layout_width="match_parent"
android:layout_height="match_parent"
android:layout_marginLeft="10dp"
android:layout_toRightOf="@id/iv_img_04"
android:gravity="center_vertical"
android:text="第四个Item" />
</RelativeLayout>
<RelativeLayout
android:layout_width="wrap_content"
android:layout_height="50dp"
android:layout_marginBottom="10dp">
<ImageView
android:id="@+id/iv_img_05"
android:layout_width="50dp"
android:layout_height="match_parent"
android:src="@drawable/app_05" />
<TextView
android:layout_width="match_parent"
android:layout_height="match_parent"
android:layout_marginLeft="10dp"
android:layout_toRightOf="@id/iv_img_05"
android:gravity="center_vertical"
android:text="第五个Item" />
</RelativeLayout>
</LinearLayout>
</RelativeLayout>
其中的菜单可以使用ListView去布局,这里做测试就没有去使用了
免责声明:本站发布的内容(图片、视频和文字)以原创、转载和分享为主,文章观点不代表本网站立场,如果涉及侵权请联系站长邮箱:is@yisu.com进行举报,并提供相关证据,一经查实,将立刻删除涉嫌侵权内容。