## (一).前言:
这几天正在更新录制实战项目,整体框架是采用仿照QQ5.X侧滑效果的。那么我们一般的做法就是自定义ViewGroup或者采用开源项目MenuDrawer或者Google提供的控件DrawerLayout等方式来实现。这些的控件的很多效果基本上都是采用实现onInterceptTouchEvent和onTouchEvent这两个方法进行实现,而且都是根据要实现的效果做自定义处理例如:多点触控处理,加速度方面检测以及控制等等。一般这样做作于普通开发人员来讲也是需要很强的程序与逻辑开发能力,幸好Android开发框架给我们提供了一个组件ViewDragHelper。上一讲我们已经就ViewGragHelper的基本使用做了相关讲解[(点击进入)](http://blog.csdn.net/developer_jiangqq/article/details/50033453),今天来分析实现QQ5.X侧滑酷炫效果的实例(开源项目地址[点击进入](https://github.com/BlueMor/DragLayout))。
具体代码已经上传到下面的项目中,欢迎各位去star和fork一下。
[https://github.com/jiangqqlmj/ViewDragHelperTest](https://github.com/jiangqqlmj/ViewDragHelperTest)
FastDev4Android框架项目地址:[https://github.com/jiangqqlmj/FastDev4Android](https://github.com/jiangqqlmj/FastDev4Android)
## (二).ViewDragHelper的基本使用
前面我们学习ViewDragHelper的基本使用方法,同时也知道了里边的若干个方法的用途,下面我们还是把基本的使用步骤温习一下。要使用ViewDragHelper实现子View拖拽移动的步骤如下:
1. 创建ViewDragHelper实例(传入Callback)
2. 重写事件拦截处理方法onInterceptTouch和onTouchEvent
3. 实现Callback,实现其中的相关方法tryCaptureView以及水平或者垂直方向移动的距离方法
更加具体分析大家可以看前一篇博客,或者我们今天这边会通过具体实例讲解一下。
## (三).QQ5.X侧滑效果实现分析:
在正式版本QQ中的侧滑效果如下:
![](https://box.kancloud.cn/2016-01-18_569c8ec1d4e5a.jpg)
观察上面我们可以理解为两个View,一个是底部的相当于左侧功能View,另外一个是上层主功能内容View,我们在上面进行拖拽上层View或者左右滑动的时候,上层和下层的View相应进行滑动以及View大小变化,同时加入相关的动画。当然我们点击上层的View可以进行打开或者关闭侧滑菜单。
## (四).侧滑效果自定义组件实现
1.首先我们这边集成自FrameLayout创建一个自定义View DragLayout。内部的定义的一些变量如下(主要包括一些配置类,手势,ViewDragHelper实例,屏幕宽高,拖拽的子视图View等)
~~~
//是否带有阴影效果
private boolean isShowShadow = true;
//手势处理类
private GestureDetectorCompat gestureDetector;
//视图拖拽移动帮助类
private ViewDragHelper dragHelper;
//滑动监听器
private DragListener dragListener;
//水平拖拽的距离
private int range;
//宽度
private int width;
//高度
private int height;
//main视图距离在ViewGroup距离左边的距离
private int mainLeft;
private Context context;
private ImageView iv_shadow;
//左侧布局
private RelativeLayout vg_left;
//右侧(主界面布局)
private CustomRelativeLayout vg_main;
~~~
然后在内部还定义了一个回调接口主要处理拖拽过程中的一些页面打开,关闭以及滑动中的事件回调:
~~~
/**
* 滑动相关回调接口
*/
public interface DragListener {
//界面打开
public void onOpen();
//界面关闭
public void onClose();
//界面滑动过程中
public void onDrag(float percent);
}
~~~
2.开始创建ViewDragHelper实例,依然在自定义View DragLayout初始化的时候创建,使用ViewDragHelper的静态方法:
~~~
public DragLayout(Context context,AttributeSet attrs, int defStyle) {
super(context, attrs, defStyle);
gestureDetector = new GestureDetectorCompat(context, new YScrollDetector());
dragHelper =ViewDragHelper.create(this, dragHelperCallback);
}
~~~
其中create()方法创建的时候传入了一个dragHelperCallBack回调类,将会在第四点中讲到。
3.接着需要重写ViewGroup中事件方法,拦截触摸事件给ViewDragHelper内部进行处理,这样达到拖拽移动子View视图的目的;
~~~
/**
* 拦截触摸事件
* @param ev
* @return
*/
@Override
public boolean onInterceptTouchEvent(MotionEvent ev) {
return dragHelper.shouldInterceptTouchEvent(ev) &&gestureDetector.onTouchEvent(ev);
}
/**
* 将拦截的到事件给ViewDragHelper进行处理
* @param e
* @return
*/
@Override
public boolean onTouchEvent(MotionEvent e){
try {
dragHelper.processTouchEvent(e);
} catch (Exception ex) {
ex.printStackTrace();
}
return false;
}
~~~
这边我们在onInterceptTouchEvent拦截让事件从父控件往子View中转移,然后在onTouchEvent方法中拦截让ViewDragHelper进行消费处理。
4.开始自定义创建ViewDragHelper.Callback的实例dragHelperCallback分别实现一个抽象方法tryCaptureView以及重写以下若干个方法来实现侧滑功能,下面一个个来看一下。
~~~
/**
* 拦截所有的子View
* @param child Child the user isattempting to capture
* @param pointerId ID of the pointerattempting the capture
* @return
*/
@Override
public boolean tryCaptureView(View child, int pointerId) {
return true;
}
~~~
该进行拦截ViewGroup(本例中为:DragLayout)中所有的子View,直接返回true,表示所有的子View都可以进行拖拽移动。
~~~
/**
* 水平方向移动
* @param child Child view beingdragged
* @param left Attempted motion alongthe X axis
* @param dx Proposed change inposition for left
* @return
*/
@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;
}
}
~~~
实现该方法表示水平方向滑动,同时方法中会进行判断边界值,例如当上面的main view已经向左移动边界之外了,直接返回0,表示向左最左边只能x=0;然后向右移动会判断向右最远距离range,至于range的初始化后边会讲到。除了这两种情况之外,就是直接返回left即可。
~~~
/**
* 设置水平方向滑动的最远距离
* @param child Child view tocheck 屏幕宽度
* @return
*/
@Override
public int getViewHorizontalDragRange(View child) {
return width;
}
~~~
该方法有必要实现,因为该方法在Callback内部默认返回0,也就是说,如果的view的click事件为true,那么会出现整个子View没法拖拽移动的情况了。那么这边直接返回left view宽度了,表示水平方向滑动的最远距离了。
~~~
/**
* 当拖拽的子View,手势释放的时候回调的方法, 然后根据左滑或者右滑的距离进行判断打开或者关闭
* @param releasedChild
* @param xvel
* @param yvel
*/
@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 == vg_main&& mainLeft > range * 0.3) {
open();
} else if (releasedChild == vg_left&& mainLeft > range * 0.7) {
open();
} else {
close();
}
}
~~~
该方法在拖拽子View移动手指释放的时候被调用,这是会判断移动向左,向右的意图,进行打开或者关闭man view(上层视图)。下面是实现的最后一个方法:onViewPositionChanged
~~~
/**
* 子View被拖拽 移动的时候回调的方法
* @param changedView View whoseposition changed
* @param left New X coordinate of theleft edge of the view
* @param top New Y coordinate of thetop edge of the view
* @param dx Change in X position fromthe last call
* @param dy Change in Y position fromthe last call
*/
@Override
public void onViewPositionChanged(View changedView, int left, int top,
int dx, int dy) {
if (changedView == vg_main) {
mainLeft = left;
} else {
mainLeft = mainLeft + left;
}
if (mainLeft < 0) {
mainLeft = 0;
} else if (mainLeft > range) {
mainLeft = range;
}
if (isShowShadow) {
iv_shadow.layout(mainLeft, 0,mainLeft + width, height);
}
if (changedView == vg_left) {
vg_left.layout(0, 0, width,height);
vg_main.layout(mainLeft, 0,mainLeft + width, height);
}
dispatchDragEvent(mainLeft);
}
};
~~~
该方法是在我们进行拖拽移动子View的过程中进行回调,根据移动坐标位置,然后进行重新定义left view和main view。同时调用dispathDragEvent()方法进行拖拽事件相关处理分发同时根据状态来回调接口:
~~~
/**
* 进行处理拖拽事件
* @param mainLeft
*/
private void dispatchDragEvent(intmainLeft) {
if (dragListener == null) {
return;
}
float percent = mainLeft / (float)range;
//根据滑动的距离的比例,进行带有动画的缩小和放大View
animateView(percent);
//进行回调滑动的百分比
dragListener.onDrag(percent);
Status lastStatus = status;
if (lastStatus != getStatus()&& status == Status.Close) {
dragListener.onClose();
} else if (lastStatus != getStatus()&& status == Status.Open) {
dragListener.onOpen();
}
}
~~~
该方法中有一行代码float percent=mainLeft/(float)range;算到一个百分比后面会用到
5.至于子View布局的获取初始化以及宽高和水平滑动距离的大小设置方法:
~~~
/**
* 布局加载完成回调
* 做一些初始化的操作
*/
@Override
protected void onFinishInflate() {
super.onFinishInflate();
if (isShowShadow) {
iv_shadow = new ImageView(context);
iv_shadow.setImageResource(R.mipmap.shadow);
LayoutParams lp = new LayoutParams(LayoutParams.MATCH_PARENT, LayoutParams.MATCH_PARENT);
addView(iv_shadow, 1, lp);
}
//左侧界面
vg_left = (RelativeLayout)getChildAt(0);
//右侧(主)界面
vg_main = (CustomRelativeLayout)getChildAt(isShowShadow ? 2 : 1);
vg_main.setDragLayout(this);
vg_left.setClickable(true);
vg_main.setClickable(true);
}
~~~
以及控件大小发生变化回调的方法:
~~~
@Override
protected void onSizeChanged(int w, int h,int oldw, int oldh) {
super.onSizeChanged(w, h, oldw, oldh);
width = vg_left.getMeasuredWidth();
height = vg_left.getMeasuredHeight();
//可以水平拖拽滑动的距离 一共为屏幕宽度的60%
range = (int) (width * 0.6f);
}
~~~
在该方法中我们可以实时获取宽和高以及拖拽水平距离。
6.上面的所有核心代码都为使用ViewDragHelper实现子控件View拖拽移动的方法,但是根据我们这边侧滑效果还需要实现动画以及滑动过程中View的缩放效果,所以我们这边引入了一个动画开源库:该库在Github中大家可以下载下来使用一下,当然后面我会把这个库的使用单独拿出来写一篇文章讲解一下。敬请期待~
![](https://box.kancloud.cn/2016-01-18_569c8ec1f29af.jpg)
然后根据前面算出来的百分比来缩放View视图:
~~~
/**
* 根据滑动的距离的比例,进行带有动画的缩小和放大View
* @param percent
*/
private void animateView(float percent) {
float f1 = 1 - percent * 0.3f;
//vg_main水平方向 根据百分比缩放
ViewHelper.setScaleX(vg_main, f1);
//vg_main垂直方向,根据百分比缩放
ViewHelper.setScaleY(vg_main, f1);
//沿着水平X轴平移
ViewHelper.setTranslationX(vg_left,-vg_left.getWidth() / 2.3f + vg_left.getWidth() / 2.3f * percent);
//vg_left水平方向 根据百分比缩放
ViewHelper.setScaleX(vg_left, 0.5f +0.5f * percent);
//vg_left垂直方向 根据百分比缩放
ViewHelper.setScaleY(vg_left, 0.5f +0.5f * percent);
//vg_left根据百分比进行设置透明度
ViewHelper.setAlpha(vg_left, percent);
if (isShowShadow) {
//阴影效果视图大小进行缩放
ViewHelper.setScaleX(iv_shadow, f1* 1.4f * (1 - percent * 0.12f));
ViewHelper.setScaleY(iv_shadow, f1* 1.85f * (1 - percent * 0.12f));
}
getBackground().setColorFilter(evaluate(percent, Color.BLACK,Color.TRANSPARENT), Mode.SRC_OVER);
}
private Integer evaluate(float fraction,Object startValue, Integer endValue) {
int startInt = (Integer) startValue;
int startA = (startInt >> 24)& 0xff;
int startR = (startInt >> 16)& 0xff;
int startG = (startInt >> 8)& 0xff;
int startB = startInt & 0xff;
int endInt = (Integer) endValue;
int endA = (endInt >> 24) &0xff;
int endR = (endInt >> 16) &0xff;
int endG = (endInt >> 8) &0xff;
int endB = endInt & 0xff;
return (int) ((startA + (int) (fraction* (endA - startA))) << 24)
| (int) ((startR + (int)(fraction * (endR - startR))) << 16)
| (int) ((startG + (int)(fraction * (endG - startG))) << 8)
| (int) ((startB + (int)(fraction * (endB - startB))));
}
~~~
7.当然除了上面这些还缺少一个效果就是,当我们滑动过程中假如我们手指释放,按照常理来讲view就不会在进行移动了,那么这边我们需要一个加速度当我们释放之后,还能保持一定的速度,该怎么样实现呢?答案就是实现computeScroll()方法。
~~~
/**
* 有加速度,当我们停止滑动的时候,该不会立即停止动画效果
*/
@Override
public void computeScroll() {
if (dragHelper.continueSettling(true)){
ViewCompat.postInvalidateOnAnimation(this);
}
}
~~~
OK上面关于DragLayout的核心代码就差不多这么多了,下面是使用DragLayout类来实现侧滑效果啦!
## (五).侧滑效果组件使用
1.首先使用的布局文件如下:
~~~
<com.chinaztt.widget.DragLayoutxmlns:android="http://schemas.android.com/apk/res/android"
android:id="@+id/dl"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:background="@mipmap/ic_main_left_bg"
>
<RelativeLayout
android:layout_width="match_parent"
android:layout_height="match_parent"
android:paddingBottom="30dp"
android:paddingLeft="30dp"
android:paddingTop="50dp"
>
<LinearLayout
android:id="@+id/ll1"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:orientation="horizontal" >
<ImageView
android:id="@+id/iv_bottom"
android:layout_width="70dp"
android:layout_height="70dp"
android:src="@mipmap/ic_launcher" />
<LinearLayout
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:gravity="center_vertical"
android:layout_gravity="center_vertical"
android:orientation="vertical">
<TextView
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_gravity="center_vertical"
android:layout_marginLeft="20dp"
android:text="名字:jiangqqlmj"
android:textColor="#ffffff"
android:textSize="18sp" />
<TextView
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_gravity="center_vertical"
android:layout_marginLeft="20dp"
android:text="QQ:781931404"
android:textColor="#ffffff"
android:textSize="16sp" />
</LinearLayout>
</LinearLayout>
<TextView
android:id="@+id/tv_mail"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_alignParentBottom="true"
android:text="jiangqqlmj@163.com"
android:textColor="#ffffff"
android:textSize="15sp"/>
<!--中间列表-->
<ListView
android:id="@+id/lv"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_above="@id/tv_mail"
android:layout_below="@id/ll1"
android:layout_marginBottom="30dp"
android:layout_marginTop="20dp"
android:cacheColorHint="#00000000"
android:listSelector="@null"
android:divider="@null"
android:scrollbars="none"
android:textColor="#ffffff" />
</RelativeLayout>
<com.chinaztt.widget.CustomRelativeLayout
android:layout_width="match_parent"
android:layout_height="match_parent"
android:background="#FFFFFF"
>
<LinearLayout
android:layout_width="fill_parent"
android:layout_height="fill_parent"
android:orientation="vertical"
>
<RelativeLayout
android:id="@+id/rl_title"
android:layout_width="match_parent"
android:layout_height="49dp"
android:background="#e7abff"
android:gravity="bottom"
>
<RelativeLayout
android:layout_width="fill_parent"
android:layout_height="49dp"
>
<ImageView
android:id="@+id/iv_icon"
android:layout_width="40dp"
android:layout_height="40dp"
android:layout_centerVertical="true"
android:layout_marginLeft="10dp"
android:scaleType="fitXY"
android:src="@mipmap/ic_launcher"
/>
<TextView
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="ViewDragHelper实例"
android:layout_centerInParent="true"
android:textColor="#ffffff"
android:textSize="20sp"
/>
</RelativeLayout>
</RelativeLayout>
<!--中间内容后面放入Fragment-->
<FrameLayout
android:layout_width="fill_parent"
android:layout_height="fill_parent"
>
<TextView
android:id="@+id/iv_noimg"
android:layout_gravity="center"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="中间内容容器..."
/>
</FrameLayout>
</LinearLayout>
</com.chinaztt.widget.CustomRelativeLayout>
</com.chinaztt.widget.DragLayout>
~~~
该布局文件中父层View就是DragLayout,然后内部有两个RelativeLayout布局,分别充当左侧View(左侧功能)和主Main View。最后在Activity中获取DragLayout控件,添加事件监听器(DragListener)具体代码如下:
~~~
public class MainActivity extends BaseActivity {
privateDragLayout dl;
privateListView lv;
privateTextView tv_noimg;
privateImageView iv_icon, iv_bottom;
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
setStatusBar();
initDragLayout();
initView();
}
private void initDragLayout() {
dl= (DragLayout) findViewById(R.id.dl);
dl.setDragListener(new DragLayout.DragListener() {
//界面打开的时候
@Override
public void onOpen() {
}
//界面关闭的时候
@Override
public void onClose() {
}
//界面滑动的时候
@Override
public void onDrag(float percent) {
ViewHelper.setAlpha(iv_icon,1 - percent);
}
});
}
private void initView() {
iv_icon= (ImageView) findViewById(R.id.iv_icon);
iv_bottom= (ImageView) findViewById(R.id.iv_bottom);
tv_noimg= (TextView) findViewById(R.id.iv_noimg);
lv= (ListView) findViewById(R.id.lv);
lv.setAdapter(new ArrayAdapter<String>(MainActivity.this,
R.layout.item_text,new String[] { "item 01", "item 01",
"item01", "item 01", "item 01", "item 01",
"item01", "item 01", "item 01", "item 01",
"item01", "item 01", "item 01", "item 01",
"item01", "item 01", "item 01",
"item01", "item 01", "item 01", "item 01"}));
lv.setOnItemClickListener(new OnItemClickListener() {
@Override
public void onItemClick(AdapterView<?> arg0, View arg1,
intposition, long arg3) {
Toast.makeText(MainActivity.this,"ClickItem "+position,Toast.LENGTH_SHORT).show();
}
});
iv_icon.setOnClickListener(new OnClickListener() {
@Override
public void onClick(View arg0) {
dl.open();
}
});
}
@Override
protectedvoid onResume() {
super.onResume();
}
}
~~~
最终运行效果如下:
![](https://box.kancloud.cn/2016-01-18_569c8ec21b964.jpg)
因为这边底层需要ViewDragHelper类,所以大家在使用的时候需要导入V4包的,不过我这边直接把ViewDragHelper类的源代码复制到项目中了。
![](https://box.kancloud.cn/2016-01-18_569c8ec2c7abd.jpg)
## (六).DragLayout源代码带注释
上面主要分析DragLayout的具体实现,不过我这边也贴一下DragLayout带有注释的全部源代码让大家可以更好的了解DragLayout的具体实现代码:
~~~
/**
* 使用ViewRragHelper实现侧滑效果功能
*/
public class DragLayout extends FrameLayout {
private boolean isShowShadow = true;
//手势处理类
private GestureDetectorCompat gestureDetector;
//视图拖拽移动帮助类
private ViewDragHelper dragHelper;
//滑动监听器
private DragListener dragListener;
//水平拖拽的距离
private int range;
//宽度
private int width;
//高度
private int height;
//main视图距离在ViewGroup距离左边的距离
private int mainLeft;
private Context context;
private ImageView iv_shadow;
//左侧布局
private RelativeLayout vg_left;
//右侧(主界面布局)
private CustomRelativeLayout vg_main;
//页面状态 默认为关闭
private Status status = Status.Close;
public DragLayout(Context context) {
this(context, null);
}
public DragLayout(Context context,AttributeSet attrs) {
this(context, attrs, 0);
this.context = context;
}
public DragLayout(Context context,AttributeSet attrs, int defStyle) {
super(context, attrs, defStyle);
gestureDetector = new GestureDetectorCompat(context, new YScrollDetector());
dragHelper =ViewDragHelper.create(this, dragHelperCallback);
}
class YScrollDetector extends SimpleOnGestureListener {
@Override
public boolean onScroll(MotionEvent e1,MotionEvent e2, float dx, float dy) {
return Math.abs(dy) <=Math.abs(dx);
}
}
/**
* 实现子View的拖拽滑动,实现Callback当中相关的方法
*/
private ViewDragHelper.CallbackdragHelperCallback = new ViewDragHelper.Callback() {
/**
* 水平方向移动
* @param child Child view beingdragged
* @param left Attempted motion alongthe X axis
* @param dx Proposed change inposition for left
* @return
*/
@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;
}
}
/**
* 拦截所有的子View
* @param child Child the user isattempting to capture
* @param pointerId ID of the pointerattempting the capture
* @return
*/
@Override
public boolean tryCaptureView(View child, int pointerId) {
return true;
}
/**
* 设置水平方向滑动的最远距离
* @param child Child view tocheck 屏幕宽度
* @return
*/
@Override
public int getViewHorizontalDragRange(View child) {
return width;
}
/**
* 当拖拽的子View,手势释放的时候回调的方法, 然后根据左滑或者右滑的距离进行判断打开或者关闭
* @param releasedChild
* @param xvel
* @param yvel
*/
@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 == vg_main&& mainLeft > range * 0.3) {
open();
} else if (releasedChild == vg_left&& mainLeft > range * 0.7) {
open();
} else {
close();
}
}
/**
* 子View被拖拽 移动的时候回调的方法
* @param changedView View whoseposition changed
* @param left New X coordinate of theleft edge of the view
* @param top New Y coordinate of thetop edge of the view
* @param dx Change in X position fromthe last call
* @param dy Change in Y position fromthe last call
*/
@Override
public void onViewPositionChanged(View changedView, int left, int top,
int dx, int dy) {
if (changedView == vg_main) {
mainLeft = left;
} else {
mainLeft = mainLeft + left;
}
if (mainLeft < 0) {
mainLeft = 0;
} else if (mainLeft > range) {
mainLeft = range;
}
if (isShowShadow) {
iv_shadow.layout(mainLeft, 0,mainLeft + width, height);
}
if (changedView == vg_left) {
vg_left.layout(0, 0, width,height);
vg_main.layout(mainLeft, 0,mainLeft + width, height);
}
dispatchDragEvent(mainLeft);
}
};
/**
* 滑动相关回调接口
*/
public interface DragListener {
//界面打开
public void onOpen();
//界面关闭
public void onClose();
//界面滑动过程中
public void onDrag(float percent);
}
public void setDragListener(DragListener dragListener) {
this.dragListener = dragListener;
}
/**
* 布局加载完成回调
* 做一些初始化的操作
*/
@Override
protected void onFinishInflate() {
super.onFinishInflate();
if (isShowShadow) {
iv_shadow = new ImageView(context);
iv_shadow.setImageResource(R.mipmap.shadow);
LayoutParams lp = new LayoutParams(LayoutParams.MATCH_PARENT, LayoutParams.MATCH_PARENT);
addView(iv_shadow, 1, lp);
}
//左侧界面
vg_left = (RelativeLayout)getChildAt(0);
//右侧(主)界面
vg_main = (CustomRelativeLayout)getChildAt(isShowShadow ? 2 : 1);
vg_main.setDragLayout(this);
vg_left.setClickable(true);
vg_main.setClickable(true);
}
public ViewGroup getVg_main() {
return vg_main;
}
public ViewGroup getVg_left() {
return vg_left;
}
@Override
protected void onSizeChanged(int w, int h,int oldw, int oldh) {
super.onSizeChanged(w, h, oldw, oldh);
width = vg_left.getMeasuredWidth();
height = vg_left.getMeasuredHeight();
//可以水平拖拽滑动的距离 一共为屏幕宽度的60%
range = (int) (width * 0.6f);
}
/**
* 调用进行left和main 视图进行位置布局
* @param changed
* @param left
* @param top
* @param right
* @param bottom
*/
@Override
protected void onLayout(boolean changed,int left, int top, int right, int bottom) {
vg_left.layout(0, 0, width, height);
vg_main.layout(mainLeft, 0, mainLeft +width, height);
}
/**
* 拦截触摸事件
* @param ev
* @return
*/
@Override
public boolean onInterceptTouchEvent(MotionEvent ev) {
returndragHelper.shouldInterceptTouchEvent(ev) &&gestureDetector.onTouchEvent(ev);
}
/**
* 将拦截的到事件给ViewDragHelper进行处理
* @param e
* @return
*/
@Override
public boolean onTouchEvent(MotionEvent e){
try {
dragHelper.processTouchEvent(e);
} catch (Exception ex) {
ex.printStackTrace();
}
return false;
}
/**
* 进行处理拖拽事件
* @param mainLeft
*/
private void dispatchDragEvent(int mainLeft) {
if (dragListener == null) {
return;
}
float percent = mainLeft / (float)range;
//根据滑动的距离的比例,进行带有动画的缩小和放大View
animateView(percent);
//进行回调滑动的百分比
dragListener.onDrag(percent);
Status lastStatus = status;
if (lastStatus != getStatus()&& status == Status.Close) {
dragListener.onClose();
} else if (lastStatus != getStatus()&& status == Status.Open) {
dragListener.onOpen();
}
}
/**
* 根据滑动的距离的比例,进行带有动画的缩小和放大View
* @param percent
*/
private void animateView(float percent) {
float f1 = 1 - percent * 0.3f;
//vg_main水平方向 根据百分比缩放
ViewHelper.setScaleX(vg_main, f1);
//vg_main垂直方向,根据百分比缩放
ViewHelper.setScaleY(vg_main, f1);
//沿着水平X轴平移
ViewHelper.setTranslationX(vg_left,-vg_left.getWidth() / 2.3f + vg_left.getWidth() / 2.3f * percent);
//vg_left水平方向 根据百分比缩放
ViewHelper.setScaleX(vg_left, 0.5f +0.5f * percent);
//vg_left垂直方向 根据百分比缩放
ViewHelper.setScaleY(vg_left, 0.5f +0.5f * percent);
//vg_left根据百分比进行设置透明度
ViewHelper.setAlpha(vg_left, percent);
if (isShowShadow) {
//阴影效果视图大小进行缩放
ViewHelper.setScaleX(iv_shadow, f1* 1.4f * (1 - percent * 0.12f));
ViewHelper.setScaleY(iv_shadow, f1* 1.85f * (1 - percent * 0.12f));
}
getBackground().setColorFilter(evaluate(percent, Color.BLACK,Color.TRANSPARENT), Mode.SRC_OVER);
}
private Integer evaluate(float fraction,Object startValue, Integer endValue) {
int startInt = (Integer) startValue;
int startA = (startInt >> 24)& 0xff;
int startR = (startInt >> 16)& 0xff;
int startG = (startInt >> 8)& 0xff;
int startB = startInt & 0xff;
int endInt = (Integer) endValue;
int endA = (endInt >> 24) &0xff;
int endR = (endInt >> 16) &0xff;
int endG = (endInt >> 8) &0xff;
int endB = endInt & 0xff;
return (int) ((startA + (int) (fraction* (endA - startA))) << 24)
| (int) ((startR + (int)(fraction * (endR - startR))) << 16)
| (int) ((startG + (int)(fraction * (endG - startG))) << 8)
| (int) ((startB + (int)(fraction * (endB - startB))));
}
/**
* 有加速度,当我们停止滑动的时候,该不会立即停止动画效果
*/
@Override
public void computeScroll() {
if (dragHelper.continueSettling(true)){
ViewCompat.postInvalidateOnAnimation(this);
}
}
/**
* 页面状态(滑动,打开,关闭)
*/
public enum Status {
Drag, Open, Close
}
/**
* 页面状态设置
* @return
*/
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(dragHelper.smoothSlideViewTo(vg_main, range, 0)) {
ViewCompat.postInvalidateOnAnimation(this);
}
} else {
vg_main.layout(range, 0, range * 2,height);
dispatchDragEvent(range);
}
}
public void close() {
close(true);
}
public void close(boolean animate) {
if (animate) {
//继续滑动
if(dragHelper.smoothSlideViewTo(vg_main, 0, 0)) {
ViewCompat.postInvalidateOnAnimation(this);
}
} else {
vg_main.layout(0, 0, width,height);
dispatchDragEvent(0);
}
}
}
~~~
## (七).最后总结
今天我们通过ViewDragHelper来讲解实现一个类似QQ5.x侧滑效果的组件的效果,另外该项目中也用到了沉浸式状态的效果。关于该效果的具体实现大家可以看我另一篇文章[(点击学习Android沉浸式状态栏)](http://blog.csdn.net/developer_jiangqq/article/details/49446855)
本次具体实例注释过的全部代码已经上传到Github项目中了。同时欢迎大家去Github站点进行clone或者下载浏览:
[https://github.com/jiangqqlmj/ViewDragHelperTest](https://github.com/jiangqqlmj/ViewDragHelperTest)
同时欢迎大家star和fork整个开源快速开发框架项目~
同时致谢: [https://github.com/BlueMor/DragLayout](https://github.com/BlueMor/DragLayout)
- 前言
- Android快速开发框架介绍(一)
- Android首页图片自动无限循环轮播Gallery+FlowIndicator(二)
- Android 列表下拉刷新组件PullToRefreshListView使用(三)
- Android 数据缓存器ACache的详解和使用(四)
- Android崩溃异常捕捉CustomCrash,提升用户体验(五)
- Android实现沉浸式状态栏(六)
- AndroidAnnnotations注入框架介绍和Android Studios基本配置(七)
- AndroidAnnnotations注入框架的工作原理(八)
- AndroidAnnnotations注入框架使用之注入组件Components(九)
- AndroidAnnnotations注入框架使用之Injection标签详解(十)
- AndroidAnnnotations注入框架使用之事件绑定Event Binding(十一)
- AndroidAnnnotations注入框架使用之线程处理Threading(十二)
- AndroidAnnnotations注入框架使用之第三方框架集成RoboGuice(十三)
- AndroidAnnnotations注入框架使用之第三方框架集成Otto事件总线(十四)
- AndroidAnnnotations注入框架使用之第三方框架集成OrmLite(十五)
- AndroidAnnnotations注入框架使用之最佳实践之Adapters和lists(十六)
- AndroidAnnnotations注入框架使用之最佳实践SharedPreferences(十七)
- Android MVP开发模式详解(十九)
- 消息总线EventBus的基本使用(二十)
- 消息总线EventBus源码分析以及与Otto框架对比(二十一)
- 列表头生成带文本或者字母的图片开源库TextDrawable使用和详解(二十二)
- 重写WebView网页加载以及JavaScript注入详解(二十三)
- BaseAdapterHelper的基本使用介绍,让你摆脱狂写一堆Adapter烦恼(二十四)
- BaseAdapterHelper详解源码分析,让你摆脱狂写一堆Adapter烦恼(二十五)
- Volley完全解析之基础使用(二十六)
- Volley完全解析之进阶最佳实践与二次封装(二十七)
- RecyclerView完全解析,让你从此爱上它(二十八)
- RecyclerView完全解析之打造新版类Gallery效果(二十九)
- RecyclerView完全解析之结合AA(Android Annotations)注入框架实例(三十)
- RecyclerView完全解析之下拉刷新与上拉加载SwipeRefreshLayout(三十一)
- CardView完全解析与RecyclerView结合使用(三十二)
- 神器ViewDragHelper完全解析,妈妈再也不担心我自定义ViewGroup滑动View操作啦~(三十三)
- 神器ViewDragHelper完全解析之详解实现QQ5.X侧滑酷炫效果(三十四)
- 实例解析之SwipeRefreshLayout+RecyclerView+CardView(三十五)
- HorizontalScrollView,Fragment,FragmentStatePagerAdapter打造网易新闻Tab及滑动页面效果(三十六)
- Android Design支持库TabLayout打造仿网易新闻Tab标签效果(三十七)
- 打造QQ6.X最新版本侧滑界面效果(三十八)