好了,还是老规矩,先给出效果图,这里就绘制了一个简单框架,各位看官可以任意添加自己的东西。
下面我来解释一下怎么使用我们的ViewDragHelper来实现这个效果
先给出我们的布局
<?xml version="1.0" encoding="UTF-8"?>
<com.jeason.qqmenudemo.widget.SideslipLayout xmlns:android="http://schemas.android.com/apk/res/android"
android:id="@+id/main_frame"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:background="@drawable/bg" >
<FrameLayout
android:id="@+id/left_panel"
android:layout_width="match_parent"
android:layout_height="match_parent" >
<TextView
android:ems="1"
android:layout_height="match_parent"
android:layout_width="wrap_content"
android:textColor="#FFFFFF"
android:layout_marginTop="56dp"
android:textSize="24dp"
android:layout_gravity="center"
android:text="@string/menu_block"
/>
</FrameLayout>
<com.jeason.qqmenudemo.widget.MyFrameLayout
android:id="@+id/main_panel"
android:background="@drawable/bg_3"
android:layout_width="match_parent"
android:layout_height="match_parent" >
<LinearLayout
android:layout_width="match_parent"
android:layout_height="match_parent"
android:orientation="vertical" >
<include layout="@layout/custom_actionbar" />
<FrameLayout
android:id="@+id/content"
android:layout_width="match_parent"
android:layout_height="match_parent">
<com.jeason.qqmenudemo.widget.MyTextView
android:id="@+id/content_block"
android:layout_width="260dp"
android:layout_height="wrap_content"
android:layout_gravity="center"
android:text="@string/content_bolock"
android:padding="8dp"
android:textSize="18dp"
/>
</FrameLayout>
</LinearLayout>
</com.jeason.qqmenudemo.widget.MyFrameLayout>
</com.jeason.qqmenudemo.widget.SideslipLayout>
我们的布局文件很简单,就是自定义了一个侧滑布局SideslipLayout,然后我们将里面的内容分为2块LeftPanel(菜单区)和MainPanel(主内容区)。
我们目前认为所有的童鞋对于ViewDragHelper都有一定的了解,不了解的可以看一下我的上一篇博文。
强大的ViewDragHelper和ViewDragHelper的妙用 一
这里,我们先给出代码,再给大家慢慢解释。
package com.jeason.qqmenudemo.widget;
import com.jeason.qqmenudemo.R;
import com.jeason.qqmenudemo.view.ViewHelper;
import android.annotation.SuppressLint;
import android.content.Context;
import android.support.v4.view.ViewCompat;
import android.support.v4.widget.ViewDragHelper;
import android.util.AttributeSet;
import android.util.Log;
import android.view.MotionEvent;
import android.view.View;
import android.widget.FrameLayout;
public class SideslipLayout extends FrameLayout{
private static final String TAG = "SideslipLayout";
@SuppressWarnings("unused")
private Context mContext;
private ViewDragHelper mDragHelper;
private DragListener mDragListener;
private int range;
private int width;
private int height;
private int mainLeft;
private FrameLayout mLeftPanel;
private MyFrameLayout mMainPanel;
private Status mDefaultStatus = Status.Close;
private Status status = mDefaultStatus;
public enum Status {
Drag, Open, Close
}
public SideslipLayout(Context context, AttributeSet attrs) {
super(context, attrs);
// TODO Auto-generated constructor stub
mContext = context;
mDragHelper = ViewDragHelper.create(this, mDragHelperCallback);
}
@Override
protected void onFinishInflate() {
mMainPanel = (MyFrameLayout) findViewById(R.id.main_panel);
mMainPanel.setDragLayout(this);
mLeftPanel = (FrameLayout) findViewById(R.id.left_panel);
}
@Override
protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
super.onMeasure(widthMeasureSpec, heightMeasureSpec);
width = mLeftPanel.getMeasuredWidth(); //which meaning Full_Window
height = mLeftPanel.getMeasuredHeight();
range = (int) (width * 0.6f);
}
@Override
public boolean onInterceptTouchEvent(MotionEvent ev) {
return mDragHelper.shouldInterceptTouchEvent(ev);
}
@SuppressLint("ClickableViewAccessibility")
@Override
public boolean onTouchEvent(MotionEvent e) {
try {
mDragHelper.processTouchEvent(e);
} catch (Exception ex) {
ex.printStackTrace();
}
return true;
}
public Status getStatus() {
if (mainLeft == 0) {
status = Status.Close;
} else if (mainLeft == range) {
status = Status.Open;
} else {
status = Status.Drag;
}
return status;
}
public void open() {
open(true);
}
public void open(boolean animate) {
if (animate) {
if (mDragHelper.smoothSlideViewTo(mMainPanel, range, 0)) {
ViewCompat.postInvalidateOnAnimation(this);
}
} else {
mMainPanel.layout(range, 0, range * 2, height);
dispatchDragEvent(range);
}
}
public void close() {
close(true);
}
public void close(boolean animate) {
if (animate) {
if (mDragHelper.smoothSlideViewTo(mMainPanel, 0, 0)) {
ViewCompat.postInvalidateOnAnimation(this);
}
} else {
mMainPanel.layout(0, 0, width, height);
dispatchDragEvent(0);
}
}
private void dispatchDragEvent(int mainLeft) {
if (mDragListener == null) {
return;
}
float percent = mainLeft / (float) range;
animateView(percent);
mDragListener.onDrag(percent);
Status lastStatus = status;
if (lastStatus != getStatus() && status == Status.Close) {
mDragListener.onClose();
} else if (lastStatus != getStatus() && status == Status.Open) {
mDragListener.onOpen();
}
}
private void animateView(float percent) {
float f1 = 1 - percent * 0.3f;
ViewHelper.setScaleX(mMainPanel, f1);
ViewHelper.setScaleY(mMainPanel, f1);
ViewHelper.setTranslationX(mLeftPanel, -mLeftPanel.getWidth() / 2.3f + mLeftPanel.getWidth() / 2.3f * percent);
ViewHelper.setScaleX(mLeftPanel, 0.5f + 0.5f * percent);
ViewHelper.setScaleY(mLeftPanel, 0.5f + 0.5f * percent);
ViewHelper.setAlpha(mLeftPanel, percent);
}
@Override
public void computeScroll() {
if (mDragHelper.continueSettling(true)) {
ViewCompat.postInvalidateOnAnimation(this);
}
}
public interface DragListener {
public void onOpen();
public void onClose();
public void onDrag(float percent);
}
public void setDragListener(DragListener dragListener) {
mDragListener = dragListener;
}
private ViewDragHelper.Callback mDragHelperCallback = new ViewDragHelper.Callback() {
@Override
public int clampViewPositionHorizontal(View child, int left, int dx) {
if (mainLeft + dx < 0) {
return 0;
} else if (mainLeft + dx > range) {
return range;
} else {
return left;
}
}
@Override
public boolean tryCaptureView(View child, int pointerId) {
return true;
}
@Override
public int getViewHorizontalDragRange(View child) {
return range;
}
@Override
public void onViewReleased(View releasedChild, float xvel, float yvel) {
super.onViewReleased(releasedChild, xvel, yvel);
if (xvel > 0) {
open();
} else if (xvel < 0) {
close();
} else if (releasedChild == mMainPanel && mainLeft > range * 0.3) {
open();
} else if (releasedChild == mLeftPanel && mainLeft > range * 0.7) {
open();
} else {
close();
}
}
@Override
public void onViewPositionChanged(View changedView, int left, int top,
int dx, int dy) {
if (changedView == mMainPanel) {
mainLeft = left;
} else {
mainLeft = mainLeft + left;
}
if (mainLeft < 0) {
mainLeft = 0;
} else if (mainLeft > range) {
mainLeft = range;
}
if (changedView == mLeftPanel) {
mLeftPanel.layout(0, 0, width, height);
mMainPanel.layout(mainLeft, 0, mainLeft + width, height);
}
dispatchDragEvent(mainLeft);
}
};
}
有我的上一篇文章我们知道,任何特效的View都最终是要通过onInterceptTouchEvent和onTouchEvent来实现的,所以首先我们需要将我们的MotionEvent交给我们的ViewDragHelper 实现
@Override
public boolean onInterceptTouchEvent(MotionEvent ev) {
return mDragHelper.shouldInterceptTouchEvent(ev);
}
@SuppressLint("ClickableViewAccessibility")
@Override
public boolean onTouchEvent(MotionEvent e) {
try {
mDragHelper.processTouchEvent(e);
} catch (Exception ex) {
ex.printStackTrace();
}
return true;
}
这里,我们一般最好采用OnTouchEvent中return true;当然如果我们的ChildView有可能需要消耗MotionEvent,那么我们需要在ChildView的OnTouchEvent 中Action_Down 中return true;(如果ChildView在Action_Down return false ;那么最终 MotionEvent走到ParentView always return true;那么ChildView 将无法得到后续的MotionEvent);
当然,考虑另一种情况,如果我们持续让我们的ParentView 中return false;那么我们可以假设如果此次的MotionEvent 最终没有被消耗(所有的ChildView都 return false ,那么我们的ParentView最终会得到Action_Down但是由于我们在Action_Down 返回了false;name 在得到Action_Down之后不会继续得到Action_UP(这里假设我们的ParentVIew是最上层ViewGroup),那么dispatchTouchEvent返回了false,事件被取消了,那么你会发现我们的ChildView不能够再移动了),这里可以说是ViewDragHelper需要考虑的逻辑问题。
当让如果我们需要我们的ChildView可能也要Action_Move时,那么就需要我们严格把控onInterceptTouchEvent,因为如果我们直接将我们的MotionEvent交给我们的ViewDragHelper,在这种情况下,因为我们的ChildView是可以移动的,所以onInterceptTouchEvent直接就返回了true,ChildView就没有机会了。那么我们可以怎么做呢。比如
@Override
public boolean onInterceptTouchEvent(MotionEvent ev) {
boolean result;
if(ev.getAction() == MotionEvent.ACTION_MOVE){
if(mChildView.requestTouchEvent(ev)){
result = false;
}else{
result = mDragHelper.shouldInterceptTouchEvent(ev);
}
}else{
result = mDragHelper.shouldInterceptTouchEvent(ev);
}
return result;
}
这里仅仅给大家做一个说明,避免复杂情况下大家发现我们的ViewDragHelper出现各种不是那么灵敏的情形,或者不是那么容易掌控。反正这里还是要提醒大家一定要充分理解onInterceptTouchEvent和onTouchEvent和dispatchTouchEvent之间的逻辑关系。
好的,走到这里我们才完成了将我们的移动处理逻辑交给了ViewDragHelper。按照我上次分析的我们的移动实现要交给我们的CallBack
private ViewDragHelper.Callback mDragHelperCallback = new ViewDragHelper.Callback() {
@Override
public int clampViewPositionHorizontal(View child, int left, int dx) {
if (mainLeft + dx < 0) { //计算ChildView左边界
return 0;
} else if (mainLeft + dx > range) {
return range;
} else {
return left;
}
}
@Override
public boolean tryCaptureView(View child, int pointerId) { //这里表示我们的Left和Main都可以移动
return true;
}
@Override
public int getViewHorizontalDragRange(View child) { //框定水平移动范围
return range;
}
@Override
public void onViewReleased(View releasedChild, float xvel, float yvel) { //移动结束后判断开合状态自动切换状态
super.onViewReleased(releasedChild, xvel, yvel);
if (xvel > 0) {
open();
} else if (xvel < 0) {
close();
} else if (releasedChild == mMainPanel && mainLeft > range * 0.3) {
open();
} else if (releasedChild == mLeftPanel && mainLeft > range * 0.7) {
open();
} else {
close();
}
}
@Override
public void onViewPositionChanged(View changedView, int left, int top,
int dx, int dy) {
if (changedView == mMainPanel) {
mainLeft = left;
} else {
mainLeft = mainLeft + left;
}
if (mainLeft < 0) {
mainLeft = 0;
} else if (mainLeft > range) {
mainLeft = range;
}
if (changedView == mLeftPanel) { //移动Left时候,保持Left不动改变Main的位置
mLeftPanel.layout(0, 0, width, height);
mMainPanel.layout(mainLeft, 0, mainLeft + width, height);
}
dispatchDragEvent(mainLeft); //开启移动的时候的动画效果缩放等
}
};
分析完了CallBack,好像就没有什么说的了,源码下载
http://download.youkuaiyun.com/download/jaysong2012/8961751
下面继续给大家分享的更复杂一点的ViewDragHelper的妙用,有兴趣的话大家可以尝试一下。重点提示一下在onInterceptTouchEvent中的处理,有机会的话再把源码共享给大家。