QQ5.0新特效

​在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 表示不处理事件
*/
@Override
public 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 不可以被拖拽
*/
@Override
public boolean tryCaptureView(View child, int arg1)
{
return true;
}
/**
* 2.获取被点击视图的水平方向的拖拽范围(不影响拖拽,但是决定了执行动画的的速度)
*/
@Override
public int getViewHorizontalDragRange(View child)
{
return mRange;
}
/**
* 3.该方法会在视图移动的时候高频率调用
* @ return left 决定视图水平移动的距离
* @ param child 被点击的视图
* @ param left 被点击视图移动后的左边距
* @ param dx 被点击视图的移动的距离,左往右移为正,右往左移为负
* 最后left = child.getLeft() + dx
*
*/
@Override
public 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 竖直拖动的距离
*/
@Override
public 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不能超过mRange
mTempLeft = 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 竖直方向的滑动速度
*/
@Override
public 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 自动化播放状态,系统自己定义动画的播放
*/
@Override
public 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" >
 
<!-- 左页面 -->
<LinearLayout
android:layout_width="match_parent"
android:layout_height="match_parent"
android:orientation="vertical"
android:paddingBottom="50dp"
android:paddingLeft="10dp"
android:paddingRight="50dp"
android:paddingTop="50dp" >
 
<ImageView
android:layout_width="50dp"
android:layout_height="50dp"
android:src="@drawable/head" />
 
<ListView
android:id="@+id/lv_left"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:scrollingCache="false" />
</LinearLayout>
 
<!-- 主页面 -->
<com.example.qq.drag.MyLinearLayout
android:id="@+id/my_ll"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:background="#ffffff"
android:orientation="vertical" >
 
<RelativeLayout
android:layout_width="match_parent"
android:layout_height="50dp"
android:background="#18B4ED"
android:gravity="center_vertical" >
 
<ImageView
android:id="@+id/head"
android:layout_width="30dp"
android:layout_height="30dp"
android:layout_marginLeft="10dp"
android:src="@drawable/head" />
</RelativeLayout>
 
<ListView
android: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 不可以被拖拽
*/
@Override
public boolean tryCaptureView(View child, int arg1)
{
return true;
}
/**
* 2.获取被点击视图的水平方向的拖拽范围(不影响拖拽,但是决定了执行动画的的速度)
*/
@Override
public int getViewHorizontalDragRange(View child)
{
return mRange;
}
/**
* 3.该方法会在视图移动的时候高频率调用
* @ return left 决定视图水平移动的距离
* @ param child 被点击的视图
* @ param left 被点击视图移动后的左边距
* @ param dx 被点击视图的移动的距离,左往右移为正,右往左移为负
* 最后left = child.getLeft() + dx
*
*/
@Override
public 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 竖直拖动的距离
*/
@Override
public 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不能超过mRange
mTempLeft = 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 竖直方向的滑动速度
*/
@Override
public 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 自动化播放状态,系统自己定义动画的播放
*/
@Override
public 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.jar
ViewHelper.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的父View
ViewCompat.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的父View
ViewCompat.postInvalidateOnAnimation(this);
}
}else {
mMainContent.layout(finalLeft, 0, mWidth+finalLeft, mHeight);
}
}
/**
* 对动画进行自动的播放,由系统Scroller默认实现,频繁调用
*/
@Override
public 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 表示不处理事件
*/
@Override
public boolean onTouchEvent(MotionEvent event)
{
mDragHelper.processTouchEvent(event);
return true;
}
/**
* 当该控件被填充之后会调用的方法
*/
@Override
protected 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);
}
/**
* 当被操作的控件尺寸(宽高)发生变化的时候调用的方法
* 该方法再控件拖拽的时候会频繁调用
*/
@Override
protected 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;
 
@Override
protected 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);
//左页面的ListVIew
mLeftList.setAdapter(new ArrayAdapter<String>(this, android.R.layout.simple_list_item_1, Cheeses.sCheeseStrings){
@Override
public 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;
}
});
//主页面的ListView
mMainList.setAdapter(new ArrayAdapter<String>(this, android.R.layout.simple_list_item_1, Cheeses.NAMES));
mMainList.setOnItemClickListener(new OnItemClickListener() {
@Override
public 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() {
@Override
public void onOpen()
{
mLeftList.smoothScrollToPosition(new Random().nextInt(50));
}
@Override
public void onDraging(float fraction)
{
ViewHelper.setAlpha(mHead, 1-fraction);
}
@Override
public 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() {
@Override
public void onClick(View v) {
mDragLayout.open();
}
};
}
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值