转载请标明原地址:http://blog.csdn.net/gaolei1201/article/details/50404941
首先介绍本篇文章的重点:
1.视觉差侧滑菜单,即菜单和主界面都滑动,其实也比较常见。有开源项目SlidingMenu,但太复杂了,且在我使用过程中无法完美实现透明状态栏,中间会有一条分割线。看我简单实现
2.Activity视觉差动画
3.支持侧滑销毁Activity,且不用担心被子控件消耗手势监听事件
其实我也是借鉴别人的,自己发明创造毕竟毕竟困难。正所谓天下文章一大抄,看你会抄不会抄,呵呵。
第一种方法:侧滑菜单SlidingMenu继承HorizontalScrollView,手势滑动。
第二种方法 :侧滑菜单SlidingMenu继承ViewGroup,放入菜单和内容两个子控件,根据手势滑动。
第三种方法:利用ViewDragHelper,可参考:http://blog.csdn.net/developer_jiangqq/article/details/50253925
效果图:
下面是第一种方法侧滑菜单主要代码,里面有注释:
<span style="font-size:14px;"><span style="font-size:14px;">package com.gaolei.slidingmenu;
import android.content.Context;
import android.content.res.TypedArray;
import android.util.AttributeSet;
import android.util.DisplayMetrics;
import android.util.TypedValue;
import android.view.MotionEvent;
import android.view.View;
import android.view.ViewGroup;
import android.view.WindowManager;
import android.widget.HorizontalScrollView;
import android.widget.LinearLayout;
import com.example.zhy_slidingmenu.R;
import com.nineoldandroids.view.ViewHelper;
public class SlidingMenu extends HorizontalScrollView {
/**
* 屏幕宽度
*/
private int mScreenWidth;
/**
* dp
*/
private int mMenuRightPadding;
/**
* 菜单的宽度
*/
private int mMenuWidth;
private int mHalfMenuWidth;
public static boolean isOpen;
private boolean once;
private ViewGroup mMenu;
private ViewGroup mContent;
public SlidingMenu(Context context, AttributeSet attrs) {
this(context, attrs, 0);
}
public SlidingMenu(Context context, AttributeSet attrs, int defStyle) {
super(context, attrs, defStyle);
WindowManager mWM = ((WindowManager) context
.getSystemService(Context.WINDOW_SERVICE));
DisplayMetrics mDisplayMetrics = new DisplayMetrics();
mWM.getDefaultDisplay().getMetrics(mDisplayMetrics);
mScreenWidth = mDisplayMetrics.widthPixels;
TypedArray a = context.getTheme().obtainStyledAttributes(attrs,
R.styleable.SlidingMenu, defStyle, 0);
int n = a.getIndexCount();
for (int i = 0; i < n; i++) {
int attr = a.getIndex(i);
switch (attr) {
case R.styleable.SlidingMenu_rightPadding:
// 默认50
mMenuRightPadding = a.getDimensionPixelSize(attr,
(int) TypedValue.applyDimension(
TypedValue.COMPLEX_UNIT_DIP, 50f,
getResources().getDisplayMetrics()));// 默认为10DP
// Log.d("gaolei","mMenuRightPadding-------------"+mMenuRightPadding);
break;
}
}
a.recycle();
}
public SlidingMenu(Context context) {
this(context, null, 0);
}
@Override
protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
/**
* 显示的设置一个宽度
*/
if (!once) {
LinearLayout wrapper = (LinearLayout) getChildAt(0);
mMenu = (ViewGroup) wrapper.getChildAt(0);
mContent = (ViewGroup) wrapper.getChildAt(1);
mMenuWidth = mScreenWidth - mMenuRightPadding;
mHalfMenuWidth = mMenuWidth / 2;
mMenu.getLayoutParams().width = mMenuWidth;
mContent.getLayoutParams().width = mScreenWidth;
}
super.onMeasure(widthMeasureSpec, heightMeasureSpec);
}
@Override
protected void onLayout(boolean changed, int l, int t, int r, int b) {
super.onLayout(changed, l, t, r, b);
if (changed) {
// 将菜单隐藏
this.scrollTo(mMenuWidth, 0);
once = true;
}
}
@Override
public boolean onTouchEvent(MotionEvent ev) {
int action = ev.getAction();
switch (action) {
// Up时,进行判断,如果显示区域大于菜单宽度一半则完全显示,否则隐藏
case MotionEvent.ACTION_UP:
int scrollX = getScrollX();
if (scrollX > mHalfMenuWidth) {
this.smoothScrollTo(mMenuWidth, 0);
isOpen = false;
MainActivity.shadow_layout.setVisibility(View.GONE);
} else {
this.smoothScrollTo(0, 0);
isOpen = true;
MainActivity.shadow_layout.setVisibility(View.VISIBLE);
}
return true;
case MotionEvent.ACTION_MOVE:
break;
}
return super.onTouchEvent(ev);
}
// 这里是拦截菜单布局滑动
public boolean onInterceptTouchEvent(MotionEvent ev) {
switch (ev.getAction()) {
case MotionEvent.ACTION_MOVE:
final float curX = ev.getX();
final float curY = ev.getY();
if (curY > 0 && curY < 500) {
return false;
}
if (isOpen && curX < mMenuWidth) {
return false;
}
}
return super.onInterceptTouchEvent(ev);
}
/**
* 打开菜单
*/
public void openMenu() {
if (isOpen)
return;
this.smoothScrollTo(0, 0);
isOpen = true;
MainActivity.shadow_layout.setVisibility(View.VISIBLE);
}
/**
* 关闭菜单
*/
public void closeMenu() {
if (isOpen) {
this.smoothScrollTo(mMenuWidth, 0);
isOpen = false;
MainActivity.shadow_layout.setVisibility(View.GONE);
}
}
/**
* 切换菜单状态
*/
public void toggle() {
if (isOpen) {
closeMenu();
} else {
openMenu();
}
}
@Override
protected void onScrollChanged(int l, int t, int oldl, int oldt) {
super.onScrollChanged(l, t, oldl, oldt);
float scale = l * 1.0f / mMenuWidth; // 这段代码是最重要的调用nineoldandroids.jar 来实现菜单视觉差效果,自己可改动 0.7f 试试
ViewHelper.setTranslationX(mMenu, mMenuWidth * scale * 0.7f);
}
}</span>
下面是第二种方法SlidingMenu自定义ViewGroup实现方法:
<span style="font-size:14px;">public class SlideLayout extends ViewGroup {
private static String TAG = "SlideMenuLayout";
private Context mContext;
private Scroller mScroller; //Android 提供的滑动辅助类
private int mTouchSlop = 0 ; //在被判定为滚动之前用户手指可以移动的最大值
private VelocityTracker mVelocityTracker; //用于计算手指滑动的速度
public static final int SNAP_VELOCITY = 200; //滚动显示和隐藏左侧布局时,手指滑动需要达到的速度:每秒200个像素点
private int mMaxScrollX = 0; //最大滚动距离,等于menu的宽度
private int menuWidth=0;
private int scrollX=0;
private int oldX;
public static boolean clickMenuToClose=false;
public void setMaxScrollX(int maxScrollX) {
this.mMaxScrollX = maxScrollX;
}
private float mDownX; //一次按下抬起的动作中,按下时的X坐标,用于和抬起时的X比较,判断移动距离。少于mTouchSlop则判定为原地点击
private float mLastX; //记录滑动过程中的X坐标
private boolean isMenuOpen = false; //菜单界面是否被打开,只有完全打开才为true
public boolean isMenuOpen() {
return isMenuOpen;
}
private boolean isTouchFinished = true;
private View mContent;
public SlideLayout(Context context, AttributeSet attrs) {
super(context, attrs);
mContext = context;
init();
}
private void init() {
Log.v(TAG, "init start");
mScroller = new Scroller(mContext);
mTouchSlop = ViewConfiguration.get(getContext()).getScaledTouchSlop();
}
@Override
public void computeScroll() {
if (mScroller.computeScrollOffset()) {
scrollTo(mScroller.getCurrX(), mScroller.getCurrY());
scrollX=mScroller.getCurrX();
// 这里是判断打开菜单时即向右滑动菜单向右滑动 实现视觉差滑动效果,scrollX就是监听startScroll开始滚动程X的值,因为getScrollX()只是监听手在屏幕上滑动的过程,手离开后就要在这里监听,0.7是可以根据需求改动
if(scrollX<oldX){
// Log.d("gaolei", "scrollX2--------------------------------|"+scrollX);
ViewHelper.setTranslationX(getChildAt(0), (menuWidth+scrollX)* 0.7f);
}
// 这里是判断点击菜单按钮关闭时,对菜单做处理,已达到视觉差效果,不然的话没有
if(clickMenuToClose){
ViewHelper.setTranslationX(getChildAt(0), (menuWidth+scrollX)* 0.7f);
}
oldX=scrollX;
postInvalidate();
}
}
@Override
protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
int width = MeasureSpec.getSize(widthMeasureSpec);
int height = MeasureSpec.getSize(heightMeasureSpec);
setMeasuredDimension(width, height);
int childCount = getChildCount();
for (int i = 0; i < childCount; i++) {
View child = getChildAt(i);
child.measure(widthMeasureSpec, heightMeasureSpec);
}
}
@Override
protected void onLayout(boolean changed, int l, int t, int r, int b) {
mContent = getChildAt(1);
if (mContent != null) {
mContent.layout(l, t, l + mContent.getMeasuredWidth(), t + mContent.getMeasuredHeight());
}
menuWidth = 0;
View menu = getChildAt(0);
if (menu != null) {
ViewGroup.LayoutParams layoutParams = menu.getLayoutParams();
menuWidth = layoutParams.width;
menu.layout(l - menuWidth, t, l, t + menu.getMeasuredHeight());
}
}
@Override
public boolean onTouchEvent(MotionEvent event) {
createVelocityTracker(event);
int curScrollX = getScrollX();
// 检查触摸点是否在滑动布局(内容content)中,如果不是则返回false,即本View不处理该事件
if (mContent != null && isTouchFinished) {
Rect rect = new Rect();
mContent.getHitRect(rect);
if (!rect.contains((int)event.getX() + curScrollX, (int)event.getY())) {
return false;
}
}
float x = event.getX(); //取得本次event的X坐标
switch (event.getAction()) {
case MotionEvent.ACTION_DOWN:
mDownX = x;
mLastX = x;
isTouchFinished = false;
break;
case MotionEvent.ACTION_MOVE:
int deltaX = (int)(mLastX - x);
if((curScrollX + deltaX) < -mMaxScrollX) {
deltaX = -mMaxScrollX - curScrollX;
}
if((curScrollX + deltaX) > 0){
deltaX = -curScrollX;
}
// 这里是判断打手势滑动开菜单时即向右滑动菜单向右滑动 实现视觉差滑动效果,menuWidth+getScrollX()是菜单滑动时X的变化,手离开后就要在这里监听,0.7是可以根据需求改动
if (deltaX != 0) {
scrollBy(deltaX, 0);
}
//这句话一定要放到scrollBy后面 不然你快速滑动左边会有空白闪现<pre code_snippet_id="1564370" snippet_file_name="blog_20160125_2_2220112" name="code" class="html"> ViewHelper.setTranslationX(getChildAt(0), (menuWidth+getScrollX())* 0.7f);</span>
下面要讲一下,手势滑动Activity边缘销毁Activity,主要难点是:里面子控件会拦截手势事件使你监听不到,那么你也就不能触发销毁Activity,看看哥是咋弄的
<span style="font-size:14px;"><span style="font-size:14px;">/**
* 自定义RelativeLayout 拦截ListView监听事件
*/
public class CustomRelativeLayout extends RelativeLayout {
private FinishActivityListener finishActivityListener;
private int downX;
public void setFinishActivityListener(FinishActivityListener finishActivityListener) {
this.finishActivityListener = finishActivityListener;
}
public CustomRelativeLayout(Context context) {
super(context);
}
public CustomRelativeLayout(Context context, AttributeSet attrs) {
super(context, attrs);
}
public CustomRelativeLayout(Context context, AttributeSet attrs, int defStyle) {
super(context, attrs, defStyle);
}
//onTouchEvent()是获取不到手势事件的,因为被ListView消耗了;只有在这判断手势监听事件 ,才能提前ListView获得
public boolean onInterceptTouchEvent(MotionEvent event) {
// TODO Auto-generated method stub
//说明将onTouch拦截在此控件,进而执行此控件的onTouchEvent
switch (event.getAction()) {
case MotionEvent.ACTION_DOWN:
downX = (int) event.getRawX();
break;
case MotionEvent.ACTION_MOVE:
int moveX = (int) event.getRawX();
Log.d("gaolei", "moveX-----------------" + moveX);
Log.d("gaolei", "downX-----------------" + downX);
//这里是判断最小滑动5,然后滑动边缘0~50彩触发销毁Activity,滑动屏幕中间不销毁
if (moveX - downX > 5 && downX < 50) {
finishActivityListener.onFinishActivity();
}
break;
}
return super.onInterceptTouchEvent(event);
}
}
</span></span>
1、视觉差视觉差侧滑菜单SlidingMenu还可以参考其它侧滑菜单实现方式:http://blog.csdn.net/developer_jiangqq/article/details/50253925
2、自定义 View实现类似系统DrawerLayout覆盖型菜单的几种方式,代码地址:https://github.com/gaoleiandroid1201/DrawerLayout