在QQ5.0 的新特性中主要是在两个页面中进行动画特效的显示,如图所示
要实现这个效果,就得用一个叫做ViewDragHelper来操作了,该类是在比较高版本的V4包中才有,里面封装了Scroller,关于Scroller请访问我的另一篇博客
好了,现在来讲一讲ViewDragHelper这个类吧!!
创建ViewDragHelper的代码如下:
mDragHelper = ViewDragHelper.create(this, mCallback);
在我们自定义的控件中(当然是继承ViewGroup)你想要用ViewDragHelper来操作你自定义的控件的话你就得把自定义控件的拦截事件的方法和触发事件的方法交给ViewDragHelper,代码如下:
/*** 让ViewDragHelper去处理用户的点击事件* @return true 表示要拦截事件,不会让子View去获取事件,而是自己调用OnTouchEvent方法决定是否消耗掉事件*/public boolean onInterceptTouchEvent(android.view.MotionEvent ev) {return mDragHelper.shouldInterceptTouchEvent(ev);};/*** 让ViewDragHelper处理触摸事件* @return true 表示自己处理事件* @return false 表示不处理事件*/@Overridepublic boolean onTouchEvent(MotionEvent event){mDragHelper.processTouchEvent(event);return true;}
详细的注释已经给上了!!!!
下面就来讲讲ViewDragHelper中关于这个CallBack吧!
这个CallBack是在对ViewDragHelper进行操作的时候进行回掉的,在创建的时候需要重写相应的代码,代码如下,针对每一个方法都有详细的注释了
/*** 关于CallBack回调是在对ViewDragHelper进行操作的时候执行的回调* 由于在onInterceptTouchEvent()方法和OnTouchEvent方法中DragLayout视图的触摸都交给了ViewDragHelper处理,所以在触摸DragLayout控件的* 时候会回调CallBack中的相应回调函数*/ViewDragHelper.Callback mCallback = new ViewDragHelper.Callback() {/*** 1.决定当前被点击的View是否可以被拖拽* @ return true 可以被拖拽* @ return false 不可以被拖拽*/@Overridepublic boolean tryCaptureView(View child, int arg1){return true;}/*** 2.获取被点击视图的水平方向的拖拽范围(不影响拖拽,但是决定了执行动画的的速度)*/@Overridepublic int getViewHorizontalDragRange(View child){return mRange;}/*** 3.该方法会在视图移动的时候高频率调用* @ return left 决定视图水平移动的距离* @ param child 被点击的视图* @ param left 被点击视图移动后的左边距* @ param dx 被点击视图的移动的距离,左往右移为正,右往左移为负* 最后left = child.getLeft() + dx**/@Overridepublic int clampViewPositionHorizontal(View child, int left, int dx){//处理当前被点击View的水平方向的拖拽范围,并且只有在主页面被点击的情况下才能移动if(child == mMainContent){if(left < 0){return 0;}else if(left > mRange){return mRange;}}return left;}/*** 4.决定当前View的位置改变之后要做的事(重要),会频繁调用** @ param changedView 被点击的View* @ param left 被点击View的拖动后的左边距* @ param top 被点击View的拖动后的上边距* @ param dx 水平拖动的距离* @ param dy 竖直拖动的距离*/@Overridepublic void onViewPositionChanged(View changedView, int left, int top, int dx, int dy){//当拖动左页面的时候,左页面位置不变,只是计算其拖动的距离,而让主页面进行相应拖动//1.首先先拿到主页面的左边距int mTempLeft = mMainContent.getLeft();//2.判断一下是否是左页面被拖动if(changedView == mLeftContent){mTempLeft += left;//左页面拖动的距离加上原来主页面的左边距就是主页面的左边距}//3.由于主页面的左边距是设定好的mRange,因此这里的mTempLeft不能超过mRangemTempLeft = mTempLeft > mRange ? mRange : mTempLeft;//4.将左页面的位置还原,并且设置主页面的位置if(changedView == mLeftContent){mLeftContent.layout(0, 0, mWidth, mHeight);mMainContent.layout(mTempLeft, 0, mTempLeft + mWidth, mHeight);}//5.将左页面移动的距离拿给主页面,让主页面进行水平和竖直方向的缩放dispatchDragEvent(mTempLeft);//6.重新绘制视图invalidate();}/*** 5.当释放点击时执行的操作** @param releaseedChild 被释放的控件* @param xvel 水平方向的滑动速度* @param yvel 竖直方向的滑动速度*/@Overridepublic void onViewReleased(View releasedChild, float xvel, float yvel){if(xvel > 1.0f){//水平滑动速度大于1则开启左页面open();}else if(Math.abs(xvel) <= 1.0f && mMainContent.getLeft() > mRange/2.0f){//水平滑动速度小于1并且主页面已经显示了一半则开启open();}else{//其它情况关闭左页面close();}}/*** 6.控件状态* STATE_IDEL 0 空闲状态* STATE_DRAGGING 1 拖动状态* STATE_SETTING 2 自动化播放状态,系统自己定义动画的播放*/@Overridepublic void onViewDragStateChanged(int state){Log.d(TAG, "状态值:"+state);super.onViewDragStateChanged(state);}};
关于以上代码只是部分关键的代码,以下是布局文件和所有的代码,运行起来就可以实现文章开始的效果了
布局文件:
<com.example.qq.drag.DragLayout xmlns:android="http://schemas.android.com/apk/res/android"xmlns:tools="http://schemas.android.com/tools"android:id="@+id/dl"android:layout_width="match_parent"android:layout_height="match_parent"android:background="@drawable/bg"tools:context=".MainActivity" ><!-- 左页面 --><LinearLayoutandroid:layout_width="match_parent"android:layout_height="match_parent"android:orientation="vertical"android:paddingBottom="50dp"android:paddingLeft="10dp"android:paddingRight="50dp"android:paddingTop="50dp" ><ImageViewandroid:layout_width="50dp"android:layout_height="50dp"android:src="@drawable/head" /><ListViewandroid:id="@+id/lv_left"android:layout_width="match_parent"android:layout_height="match_parent"android:scrollingCache="false" /></LinearLayout><!-- 主页面 --><com.example.qq.drag.MyLinearLayoutandroid:id="@+id/my_ll"android:layout_width="match_parent"android:layout_height="match_parent"android:background="#ffffff"android:orientation="vertical" ><RelativeLayoutandroid:layout_width="match_parent"android:layout_height="50dp"android:background="#18B4ED"android:gravity="center_vertical" ><ImageViewandroid:id="@+id/head"android:layout_width="30dp"android:layout_height="30dp"android:layout_marginLeft="10dp"android:src="@drawable/head" /></RelativeLayout><ListViewandroid:id="@+id/lv_main"android:layout_width="match_parent"android:layout_height="match_parent"android:scrollingCache="false" /></com.example.qq.drag.MyLinearLayout></com.example.qq.drag.DragLayout>
自定义控件的代码:
package com.example.qq.drag;import com.nineoldandroids.view.ViewHelper;import android.content.Context;import android.graphics.Color;import android.graphics.PorterDuff.Mode;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.view.ViewGroup;import android.widget.FrameLayout;/***@包名 com.example.qq.drag*@类名 DragLayout*@作者 Alan*@时间 2015-4-11 下午5:00:41**@描述 自定义拖拽的控件*/public class DragLayout extends FrameLayout{protected static final String TAG = "log";private ViewDragHelper mDragHelper;//用于操作视图的拖动器private View mLeftContent;//左页面private View mMainContent;//主页面private int mWidth;//控件宽度private int mHeight;//控件高度private int mRange;//控件被拖动的最大左边距private Status mStatus = Status.Close;//初始化拖拽视图的状态为左页面关闭状态private onDragStateChangeListener mListener;/***@描述 拖拽视图的状态*/public static enum Status{Close,Open,Draging;}public Status getStatus(){return mStatus;}/***@描述 定义拖拽视图的监听器*/public interface onDragStateChangeListener{void onClose();void onOpen();void onDraging(float fraction);}/*** 设置监听器* @param mListener*/public void setmListener(onDragStateChangeListener mListener){this.mListener = mListener;}public DragLayout(Context context) {this(context,null);}public DragLayout(Context context, AttributeSet attrs) {this(context,attrs,0);}public DragLayout(Context context, AttributeSet attrs, int defStyle) {super(context, attrs, defStyle);mDragHelper = ViewDragHelper.create(this, mCallback);}/*** 关于CallBack回调是在对ViewDragHelper进行操作的时候执行的回调* 由于在onInterceptTouchEvent()方法和OnTouchEvent方法中DragLayout视图的触摸都交给了ViewDragHelper处理,所以在触摸DragLayout控件的* 时候会回调CallBack中的相应回调函数*/ViewDragHelper.Callback mCallback = new ViewDragHelper.Callback() {/*** 1.决定当前被点击的View是否可以被拖拽* @ return true 可以被拖拽* @ return false 不可以被拖拽*/@Overridepublic boolean tryCaptureView(View child, int arg1){return true;}/*** 2.获取被点击视图的水平方向的拖拽范围(不影响拖拽,但是决定了执行动画的的速度)*/@Overridepublic int getViewHorizontalDragRange(View child){return mRange;}/*** 3.该方法会在视图移动的时候高频率调用* @ return left 决定视图水平移动的距离* @ param child 被点击的视图* @ param left 被点击视图移动后的左边距* @ param dx 被点击视图的移动的距离,左往右移为正,右往左移为负* 最后left = child.getLeft() + dx**/@Overridepublic int clampViewPositionHorizontal(View child, int left, int dx){//处理当前被点击View的水平方向的拖拽范围,并且只有在主页面被点击的情况下才能移动if(child == mMainContent){if(left < 0){return 0;}else if(left > mRange){return mRange;}}return left;}/*** 4.决定当前View的位置改变之后要做的事(重要),会频繁调用** @ param changedView 被点击的View* @ param left 被点击View的拖动后的左边距* @ param top 被点击View的拖动后的上边距* @ param dx 水平拖动的距离* @ param dy 竖直拖动的距离*/@Overridepublic void onViewPositionChanged(View changedView, int left, int top, int dx, int dy){//当拖动左页面的时候,左页面位置不变,只是计算其拖动的距离,而让主页面进行相应拖动//1.首先先拿到主页面的左边距int mTempLeft = mMainContent.getLeft();//2.判断一下是否是左页面被拖动if(changedView == mLeftContent){mTempLeft += left;//左页面拖动的距离加上原来主页面的左边距就是主页面的左边距}//3.由于主页面的左边距是设定好的mRange,因此这里的mTempLeft不能超过mRangemTempLeft = mTempLeft > mRange ? mRange : mTempLeft;//4.将左页面的位置还原,并且设置主页面的位置if(changedView == mLeftContent){mLeftContent.layout(0, 0, mWidth, mHeight);mMainContent.layout(mTempLeft, 0, mTempLeft + mWidth, mHeight);}//5.将左页面移动的距离拿给主页面,让主页面进行水平和竖直方向的缩放dispatchDragEvent(mTempLeft);//6.重新绘制视图invalidate();}/*** 5.当释放点击时执行的操作** @param releaseedChild 被释放的控件* @param xvel 水平方向的滑动速度* @param yvel 竖直方向的滑动速度*/@Overridepublic void onViewReleased(View releasedChild, float xvel, float yvel){if(xvel > 1.0f){//水平滑动速度大于1则开启左页面open();}else if(Math.abs(xvel) <= 1.0f && mMainContent.getLeft() > mRange/2.0f){//水平滑动速度小于1并且主页面已经显示了一半则开启open();}else{//其它情况关闭左页面close();}}/*** 6.控件状态* STATE_IDEL 0 空闲状态* STATE_DRAGGING 1 拖动状态* STATE_SETTING 2 自动化播放状态,系统自己定义动画的播放*/@Overridepublic void onViewDragStateChanged(int state){Log.d(TAG, "状态值:"+state);super.onViewDragStateChanged(state);}};/*** 当左边距在进行缩放的时候,这时可以根据该左边距的缩放比例进行主页面缩放的动画* @param mTempLeft*/protected void dispatchDragEvent(int mTempLeft){//该比值是主页面拖放时左边距与设置的左边距最大范围的比值float percent = mTempLeft * 1.0f / mRange;//1.执行动画animViews(percent);//2.回调if(mListener != null){mListener.onDraging(percent);}Status mLastStatus = mStatus;//原始状态mStatus = updataStatus(percent);//更新后的状态,根据percent比值来判断状态if(mStatus != mLastStatus){//状态改变了if(mListener == null){return ;}if(mStatus == Status.Close){mListener.onClose();}else if(mStatus == Status.Open){mListener.onOpen();}}}/*** 获取更新后的状态* @param mLastStatus* @return*/private Status updataStatus(float percent){if(percent == 0.0f){return Status.Close;}else if(percent == 1.0f){return Status.Open;}return Status.Draging;}/*** 主页面与左页面动画的设置* @param percent*/private void animViews(float percent){//1.主页面的水平与竖直方向的缩放动画,ViewHelper是第三方框架nineoldandroids-2.4.0.jarViewHelper.setScaleX(mMainContent, (1- percent) * 0.2f + 0.8f);ViewHelper.setScaleY(mMainContent, (1- percent) * 0.2f + 0.8f);//2.左面板的平移,缩放,透明度动ViewHelper.setTranslationX(mLeftContent, evaluate(percent, -mWidth/2.0f, 0));ViewHelper.setScaleX(mLeftContent, evaluate(percent, 0.5f, 1.0f));ViewHelper.setScaleY(mLeftContent, evaluate(percent, 0.5f, 1.0f));ViewHelper.setAlpha(mLeftContent, evaluate(percent, 0.2f, 1.0f));//3.左面板的背景变化getBackground().setColorFilter((Integer) evaluateColor(percent, Color.BLACK, Color.TRANSPARENT), Mode.SRC_OVER);}/**从开始值到结束值的过度* @param fraction 频率* @param startValue 开始值* @param endValue 结束值* @return*/public Float evaluate(float fraction, Number startValue, Number endValue) {float startFloat = startValue.floatValue();return startFloat + fraction * (endValue.floatValue() - startFloat);}/*** 从开始颜色到结束值的过度(包含透明度的过度)* @param fraction 频率* @param startValue 开始值* @param endValue 结束值* @return*/public Object evaluateColor(float fraction, Object startValue, Object 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))));}/*** 关闭主页面*/protected void close(){close(true);}/*** 开启主页面*/protected void open(){open(true);}/*** 关闭主页面是否需要平滑动画* @param isSmooth true 需要*/private void close(boolean isSmooth){int finalLeft = 0;if(isSmooth){//内部封装了Scoller滚动器,注意要结合computeScoll方法进行触发动画的开始if(mDragHelper.smoothSlideViewTo(mMainContent, finalLeft, 0)){//如果返回true的话表示要刷新界面进行动画播放,参数为要移动View的父ViewViewCompat.postInvalidateOnAnimation(this);}}else {mMainContent.layout(finalLeft, 0, mWidth+finalLeft, mHeight);}}/*** 开启主页面是否需要平滑动画* @param isSmooth true 需要*/private void open(boolean isSmooth){int finalLeft = mRange;if(isSmooth){//内部封装了Scoller滚动器,注意要结合computeScoll方法进行触发动画的开始if(mDragHelper.smoothSlideViewTo(mMainContent, finalLeft, 0)){//如果返回true的话表示要刷新界面进行动画播放,参数为要移动View的父ViewViewCompat.postInvalidateOnAnimation(this);}}else {mMainContent.layout(finalLeft, 0, mWidth+finalLeft, mHeight);}}/*** 对动画进行自动的播放,由系统Scroller默认实现,频繁调用*/@Overridepublic void computeScroll(){if(mDragHelper.continueSettling(true)){ViewCompat.postInvalidateOnAnimation(this);}}/*** 让ViewDragHelper去处理用户的点击事件* @return true 表示要拦截事件,不会让子View去获取事件,而是自己调用OnTouchEvent方法决定是否消耗掉事件*/public boolean onInterceptTouchEvent(android.view.MotionEvent ev) {return mDragHelper.shouldInterceptTouchEvent(ev);};/*** 让ViewDragHelper处理触摸事件* @return true 表示自己处理事件* @return false 表示不处理事件*/@Overridepublic boolean onTouchEvent(MotionEvent event){mDragHelper.processTouchEvent(event);return true;}/*** 当该控件被填充之后会调用的方法*/@Overrideprotected void onFinishInflate(){//这样做是为了增强代码的健壮性,在拿给别人用的时候可以让其正确使用int childCount = getChildCount();if(childCount < 2){throw new IllegalStateException("该控件的子控件必须要有两个...Children must have two!");}if( !(getChildAt(0) instanceof ViewGroup && getChildAt(1) instanceof ViewGroup)){throw new IllegalArgumentException("该控件的子控件必须是ViewGroup类型...Childre must be ViewGroup type!");}//获取左侧页面和主页面的控件mLeftContent = getChildAt(0);mMainContent = getChildAt(1);}/*** 当被操作的控件尺寸(宽高)发生变化的时候调用的方法* 该方法再控件拖拽的时候会频繁调用*/@Overrideprotected void onSizeChanged(int w, int h, int oldw, int oldh){super.onSizeChanged(w, h, oldw, oldh);//获取被点击的变化时的控件宽高mWidth = getMeasuredWidth();mHeight = getMeasuredHeight();//设置水平方向的拖拽范围mRange = (int) (mWidth * 0.6f);}}
Activity中的代码:
package com.example.qq.drag;import java.util.Random;import android.app.Activity;import android.graphics.Color;import android.os.Bundle;import android.view.View;import android.view.View.OnClickListener;import android.view.ViewGroup;import android.view.Window;import android.view.animation.CycleInterpolator;import android.widget.AdapterView;import android.widget.AdapterView.OnItemClickListener;import android.widget.ArrayAdapter;import android.widget.ListView;import android.widget.TextView;import com.example.qq.R;import com.example.qq.drag.DragLayout.onDragStateChangeListener;import com.example.qq.drag.bean.Cheeses;import com.nineoldandroids.animation.ObjectAnimator;import com.nineoldandroids.view.ViewHelper;/****@包名 com.example.qq.drag*@类名 MainActivity*@作者 Alan*@时间 2015-4-12 下午4:09:24**@描述*/public class MainActivity extends Activity {private DragLayout mDragLayout;@Overrideprotected void onCreate(Bundle savedInstanceState) {super.onCreate(savedInstanceState);requestWindowFeature(Window.FEATURE_NO_TITLE);setContentView(R.layout.activity_main);final ListView mLeftList = (ListView) findViewById(R.id.lv_left);ListView mMainList = (ListView) findViewById(R.id.lv_main);//左页面的ListVIewmLeftList.setAdapter(new ArrayAdapter<String>(this, android.R.layout.simple_list_item_1, Cheeses.sCheeseStrings){@Overridepublic View getView(int position, View convertView, ViewGroup parent) {View view = super.getView(position, convertView, parent);TextView textView = ((TextView)view);textView.setTextColor(Color.WHITE);return view;}});//主页面的ListViewmMainList.setAdapter(new ArrayAdapter<String>(this, android.R.layout.simple_list_item_1, Cheeses.NAMES));mMainList.setOnItemClickListener(new OnItemClickListener() {@Overridepublic void onItemClick(AdapterView<?> parent, View view,int position, long id) {//startActivity(new Intent(MainActivity.this, GooActivity.class));}});final View mHead = findViewById(R.id.head);mHead.setOnClickListener(mClickListener);mDragLayout = (DragLayout) findViewById(R.id.dl);mDragLayout.setmListener(new onDragStateChangeListener() {@Overridepublic void onOpen(){mLeftList.smoothScrollToPosition(new Random().nextInt(50));}@Overridepublic void onDraging(float fraction){ViewHelper.setAlpha(mHead, 1-fraction);}@Overridepublic void onClose(){ObjectAnimator mAnim = ObjectAnimator.ofFloat(mHead, "translationX", 15.0f);mAnim.setInterpolator(new CycleInterpolator(4.0f));mAnim.setDuration(1000);mAnim.start();}});MyLinearLayout myLinearLayout = (MyLinearLayout) findViewById(R.id.my_ll);myLinearLayout.setDragLayout(mDragLayout);}OnClickListener mClickListener = new OnClickListener() {@Overridepublic void onClick(View v) {mDragLayout.open();}};}
698

被折叠的 条评论
为什么被折叠?



