QQ5.0特效专辑

1.课程介绍

1.侧滑面板(对ViewGroup的自定义)
2.快速索引 (对View的自定义) 
3.侧拉删除
4.视差特效 
5.粘性控件 (对View的自定义)


2.侧滑面板_ViewDragHelper 

 1. ViewDragHelper: Google2013年IO大会提出的,
      解决界面控件拖拽移动问题. (v4包下),有些v4包版本低可能没有

 2. mTouchSlop 最小敏感范围, 值越小, 越敏感


 3.ViewDragHelper解决了android中手势处理过于复杂的问题,在DrawerLayout出现之前,侧滑菜单都是由第三方开源代码实现的,     其中著名的当属MenuDrawer ,MenuDrawer重写onTouchEvent方法来实现侧滑效果,代码量很大,实现逻辑也需要很大的耐心才     能看懂。如果每个开发人员都从这么原始的步奏开始做起,那对于安卓生态是相当不利的。所以说ViewDragHelper等的出现反映       了安卓开发框架已经开始向成熟的方向迈进。

 
4. 关联v4 jar包源码的方法-----查看源码的方法
 
5.不去继承ViewGroup,继承FrameLayout更方便,不用测量onMean()
 
6.构造函数
	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);
		
		// a.初始化 (通过静态方法) 
		mDragHelper = ViewDragHelper.create(this , mCallback);
		//参数1:父布局,就是自己        参数2:回调方法
	}

7.ViewDragHelper的构造函数为私有,所以不能直接new出来,用create创建
 
7.回调方法Callback
	ViewDragHelper.Callback mCallback = new ViewDragHelper.Callback() {
	 		
		 
}

 
8.把触摸事件传递给ViewDragHelper
	// b.传递触摸事件
	//   把布局的触摸事件传递给mDragHelper
	@Override
	public boolean onInterceptTouchEvent(MotionEvent ev) {
		// 传递给mDragHelper
		return mDragHelper.shouldInterceptTouchEvent(ev);
	}
	@Override
	public boolean onTouchEvent(MotionEvent event) {
		try {
			mDragHelper.processTouchEvent(event);
		} catch (Exception e) {
			e.printStackTrace();
		}
		// 返回true, 持续接受事件
		return true;
	}

9.找到ViewGroup里的控件布局
@Override
	protected void onFinishInflate() {
		super.onFinishInflate();
		// Github
		// 写注释
		// 容错性检查 (至少有俩子View, 子View必须是ViewGroup的子类)
		
		if(getChildCount() < 2){
			throw new IllegalStateException("布局至少有俩孩子. Your ViewGroup must have 2 children at least.");
		}
		if(!(getChildAt(0) instanceof ViewGroup && getChildAt(1) instanceof ViewGroup)){
			throw new IllegalArgumentException("子View必须是ViewGroup的子类. Your children must be an instance of ViewGroup");
		}
		
		mLeftContent = (ViewGroup) getChildAt(0);
		mMainContent = (ViewGroup) getChildAt(1);
	}

10.重写回调方法Callback
ViewDragHelper.Callback mCallback = new ViewDragHelper.Callback() {
		 // c. 重写事件
		// 1. 根据返回结果决定当前child是否可以拖拽
		// child 当前被拖拽的View
		// pointerId 区分多点触摸的id
		@Override
		public boolean tryCaptureView(View child, int pointerId) {
			Log.d(TAG, "tryCaptureView: " + child);
			//return child==mLeftContent;  规定只能主面板可以拖拽
			return true;
		};
		
		
		
			// 2. 根据建议值 修正将要移动到的(横向)位置   (重要)
		// 此时没有发生真正的移动
		public int clampViewPositionHorizontal(View child, int left, int dx) {
			// child: 当前拖拽的View
			// left 新的位置的建议值, dx 位置变化量
			return left;  //让规定的控件可以左右滑动
		}
		 
}

 
 
 
 
 
 
11.总结----重写部分重要方法
/**
 * 侧滑面板
 * @author poplar
 *
 */
public class DragLayout extends FrameLayout {

	 
	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);
		
		// a.初始化 (通过静态方法) 
		mDragHelper = ViewDragHelper.create(this , mCallback);
		
	}
	
	ViewDragHelper.Callback mCallback = new ViewDragHelper.Callback() {
		 // c. 重写事件
		// 1. 根据返回结果决定当前child是否可以拖拽
		// child 当前被拖拽的View
		// pointerId 区分多点触摸的id
		@Override
		public boolean tryCaptureView(View child, int pointerId) {
			Log.d(TAG, "tryCaptureView: " + child);
			//return child==mLeftContent;  规定只能主面板可以拖拽
			return true;
		};
		
		
		
			// 2. 根据建议值 修正将要移动到的(横向)位置   (重要)
		// 此时没有发生真正的移动
		public int clampViewPositionHorizontal(View child, int left, int dx) {
			// child: 当前拖拽的View
			// left 新的位置的建议值, dx 位置变化量
			return left;  //让规定的控件可以左右滑动
		}
		 
}
 
	
	// b.传递触摸事件
	//   把布局的触摸事件传递给mDragHelper
	@Override
	public boolean onInterceptTouchEvent(MotionEvent ev) {
		// 传递给mDragHelper
		return mDragHelper.shouldInterceptTouchEvent(ev);
	}
	@Override
	public boolean onTouchEvent(MotionEvent event) {
		try {
			mDragHelper.processTouchEvent(event);
		} catch (Exception e) {
			e.printStackTrace();
		}
		// 返回true, 持续接受事件
		return true;
	}
	
	@Override
	protected void onFinishInflate() {
		super.onFinishInflate();
		// Github
		// 写注释
		// 容错性检查 (至少有俩子View, 子View必须是ViewGroup的子类)
		
		if(getChildCount() < 2){
			throw new IllegalStateException("布局至少有俩孩子. Your ViewGroup must have 2 children at least.");
		}
		if(!(getChildAt(0) instanceof ViewGroup && getChildAt(1) instanceof ViewGroup)){
			throw new IllegalArgumentException("子View必须是ViewGroup的子类. Your children must be an instance of ViewGroup");
		}
		
		mLeftContent = (ViewGroup) getChildAt(0);
		mMainContent = (ViewGroup) getChildAt(1);
	}
 
}

3.侧滑面板_事件监听

伴随动画:
> 1. 左面板: 缩放动画, 平移动画, 透明度动画
> 2. 主面板: 缩放动画
> 3. 背景动画: 亮度变化 (颜色变化)


* 状态监听\触摸优化:
> 1. 设置并更新状态
> 2. 触摸优化: 重写ViewGroup里onInterceptTouchEvent和onTouchEvent

 

1.重写Callback中的方法

 (1)tryCaptureView ------------重要

// 1. 根据返回结果决定当前child是否可以拖拽
		// child 当前被拖拽的View
		// pointerId 区分多点触摸的id
		@Override
		public boolean tryCaptureView(View child, int pointerId) {
			Log.d(TAG, "tryCaptureView: " + child);
			//return child==mLeftContent;  规定只能主面板可以拖拽
			return true;
		};
		


(2)onViewCaptured----触摸被捕获时调用

		 @Override
		public void onViewCaptured(View capturedChild, int activePointerId) {
			Log.d(TAG, "onViewCaptured: " + capturedChild);
			// 当capturedChild被捕获时,调用.
			super.onViewCaptured(capturedChild, activePointerId);
		}


(3)getViewHorizontalDragRange----规定View滑动的范围,不对拖拽进行真正的限制,仅仅决定了动画执行速度

//规定View滑动的范围,
		@Override
		public int getViewHorizontalDragRange(View child) {
			// 返回拖拽的范围, 不对拖拽进行真正的限制. 仅仅决定了动画执行速度
			return mRange;
		}


---获取屏幕的宽度

-----------------方法一,测量完之后,即在onMeasure方法中获取

 protected void onMeasure(int widthMeasureSpec,int heightMeasureSpec){
           super.onMeaaure(widthMeasureSpec,heightMeasureSpec);
           getMea
}


-----------------方法二,onSizeChanged,当尺寸变化时调用,也是在onMeasure之后调用

	@Override
	protected void onSizeChanged(int w, int h, int oldw, int oldh) {
		super.onSizeChanged(w, h, oldw, oldh);
		// 当尺寸有变化的时候调用
		
		mHeight = getMeasuredHeight();  //屏幕的高度
		mWidth = getMeasuredWidth();
		
		// 移动的范围
		mRange = (int) (mWidth * 0.6f);
		
	}


(4)clampViewPositionHorizontal-----------------重要

// 2. 根据建议值 修正将要移动到的(横向)位置   (重要)
		// 此时没有发生真正的移动
		public int clampViewPositionHorizontal(View child, int left, int dx) {
			// child: 当前拖拽的View
			// left 新的位置的建议值, dx 位置变化量
			// left = (之前的left)oldLeft + dx;
			Log.d(TAG, "clampViewPositionHorizontal: " 
					+ "oldLeft: " + child.getLeft() + " dx: " + dx + " left: " +left);
			
                          if(left<0){//限定滑动范围
                                 return 0;
                            }else if(left>mRange){//限定滑动范围
                                return mRange;
                             }	 
			return left;
			//return child.getLeft();      布局不能拖拽,左上角在0的位置
			//return *****;             返回什么值,就移动到哪里
		}

补充:只限定某个布局:if(child == mMainContent){   }


(5)clampViewPositionVertical -------------根据建议值 修正将要移动的纵向位置

public int clampViewPositionVertical(View child,int top,int dy){
     return top;//返回建议值,让它可以上下移动
     //return ***;  返回什么值,就移动到哪里
}


(6)onViewPositionChanged--------------重要

// 3. 当View位置改变的时候, 处理要做的事情 (更新状态, 伴随动画, 重绘界面)
		// 此时,View已经发生了位置的改变
		@Override
		public void onViewPositionChanged(View changedView, int left, int top,
				int dx, int dy) {
			// changedView 改变位置的View
			// left 新的左边值
			// dx 水平方向变化量
			super.onViewPositionChanged(changedView, left, top, dx, dy);
			Log.d(TAG, "onViewPositionChanged: " + "left: " + left + " dx: " + dx);
			
			int newLeft = left;
			if(changedView == mLeftContent){
				// 把当前变化量传递给mMainContent
				newLeft = mMainContent.getLeft() + dx;
			}
			
			// 进行修正
			newLeft = fixLeft(newLeft);
			
			if(changedView == mLeftContent) {
				// 当左面板移动之后, 再强制放回去.
				mLeftContent.layout(0, 0, 0 + mWidth, 0 + mHeight);
				mMainContent.layout(newLeft, 0, newLeft + mWidth, 0 + mHeight);
			}
 
			
			// 为了兼容低版本, 每次修改值之后, 进行重绘
			invalidate();
		}


------------修正,限定范围

 

	/**
	 * 根据范围修正左边值
	 * @param left
	 * @return
	 */
	private int fixLeft(int left) {
		if(left < 0){
			return 0;
		}else if (left > mRange) {
			return mRange;
		}
		return left;
	}


 

 


*代码:
CallBack方法
ViewDragHelper.Callback mCallback = new ViewDragHelper.Callback() {
		 // c. 重写事件
		// 1. 根据返回结果决定当前child是否可以拖拽
		// child 当前被拖拽的View
		// pointerId 区分多点触摸的id
		@Override
		public boolean tryCaptureView(View child, int pointerId) {
			Log.d(TAG, "tryCaptureView: " + child);
			//return child==mLeftContent;  规定只能主面板可以拖拽
			return true;
		};
		
		
		 @Override
		public void onViewCaptured(View capturedChild, int activePointerId) {
			Log.d(TAG, "onViewCaptured: " + capturedChild);
			// 当capturedChild被捕获时,调用.
			super.onViewCaptured(capturedChild, activePointerId);
		}
		
		//规定View滑动的范围,
		@Override
		public int getViewHorizontalDragRange(View child) {
			// 返回拖拽的范围, 不对拖拽进行真正的限制. 仅仅决定了动画执行速度
			return mRange;
		}
		
 		// 2. 根据建议值 修正将要移动到的(横向)位置   (重要)
		// 此时没有发生真正的移动
		public int clampViewPositionHorizontal(View child, int left, int dx) {
			// child: 当前拖拽的View
			// left 新的位置的建议值, dx 位置变化量
			// left = (之前的left)oldLeft + dx;
			Log.d(TAG, "clampViewPositionHorizontal: " 
					+ "oldLeft: " + child.getLeft() + " dx: " + dx + " left: " +left);
			
	 
			return left;
			//return child.getLeft();      布局不能拖拽,左上角在0的位置
			//return *****;             返回什么值,就移动到哪里
		}
		
		
		
		
		
		
		// 3. 当View位置改变的时候, 处理要做的事情 (更新状态, 伴随动画, 重绘界面)
		// 此时,View已经发生了位置的改变
		@Override
		public void onViewPositionChanged(View changedView, int left, int top,
				int dx, int dy) {
			// changedView 改变位置的View
			// left 新的左边值
			// dx 水平方向变化量
			super.onViewPositionChanged(changedView, left, top, dx, dy);
			Log.d(TAG, "onViewPositionChanged: " + "left: " + left + " dx: " + dx);
			
			int newLeft = left;
			if(changedView == mLeftContent){
				// 把当前变化量传递给mMainContent
				newLeft = mMainContent.getLeft() + dx;
			}
			
			// 进行修正
			newLeft = fixLeft(newLeft);
			
			if(changedView == mLeftContent) {
				// 当左面板移动之后, 再强制放回去.
				mLeftContent.layout(0, 0, 0 + mWidth, 0 + mHeight);
				mMainContent.layout(newLeft, 0, newLeft + mWidth, 0 + mHeight);
			}
 
			
			// 为了兼容低版本, 每次修改值之后, 进行重绘
			invalidate();
		}
 
		 
}

 

4.侧滑面板_伴随动画


1.CallBack回调方法----接上面
(7)onViewReleased--------------当View被释放的时候, 处理的事情(执行动画)
         // 4. 当View被释放的时候, 处理的事情(执行动画)
		@Override
		public void onViewReleased(View releasedChild, float xvel, float yvel) {
			// View releasedChild 被释放的子View 
			// float xvel 水平方向的速度, 向右为+
			// float yvel 竖直方向的速度, 向下为+
			Log.d(TAG, "onViewReleased: " + "xvel: " + xvel + " yvel: " + yvel);
			super.onViewReleased(releasedChild, xvel, yvel);
			
			// 判断执行 关闭/开启
			// 先考虑所有开启的情况,剩下的就都是关闭的情况
			if(xvel == 0 && mMainContent.getLeft() > mRange / 2.0f){
				open();//打开超过一半是停止自动打开
			}else if (xvel > 0) {//大于0即方向向右
				open();
			}else {
				close();
			}
			
		}

(7.1)开启和关闭以及动画,平滑开启和关闭
        模板代码
/**
	 * 关闭
	 */
	public void close(boolean isSmooth) {
		int finalLeft = 0;
		if(isSmooth){
			// 1. 触发一个平滑动画
			if(mDragHelper.smoothSlideViewTo(mMainContent, finalLeft, 0)){
				// 返回true代表还没有移动到指定位置, 需要刷新界面.
				// 参数传this(child所在的ViewGroup)
				ViewCompat.postInvalidateOnAnimation(this);//刷新界面
			}
		}else {
			mMainContent.layout(finalLeft, 0, finalLeft + mWidth, 0 + mHeight);
		}
	}
 
	/**
	 * 开启
	 */
	public void open(boolean isSmooth) {
		int finalLeft = mRange;
		if(isSmooth){
			// 1. 触发一个平滑动画
			if(mDragHelper.smoothSlideViewTo(mMainContent, finalLeft, 0)){
				// 返回true代表还没有移动到指定位置, 需要刷新界面.
				// 参数传this(child所在的ViewGroup)
				ViewCompat.postInvalidateOnAnimation(this);//刷新界面
			}
		}else {
			mMainContent.layout(finalLeft, 0, finalLeft + mWidth, 0 + mHeight);
		}
	}

	@Override
	public void computeScroll() {
		super.computeScroll();
		
		// 2. 持续平滑动画 (高频率调用)
		if(mDragHelper.continueSettling(true)){
			//  如果返回true, 动画还需要继续执行
			ViewCompat.postInvalidateOnAnimation(this);
		}
	}

2.伴随动画
> 1. 左面板: 缩放动画, 平移动画, 透明度动画
> 2. 主面板: 缩放动画
> 3. 背景动画: 亮度变化 (颜色变化)
(1)动画放在CallBack中onViewPositionChanged方法中实现,
// 3. 当View位置改变的时候, 处理要做的事情 (更新状态, 伴随动画, 重绘界面)
		// 此时,View已经发生了位置的改变
		@Override
		public void onViewPositionChanged(View changedView, int left, int top,
				int dx, int dy) {
			// changedView 改变位置的View
			// left 新的左边值
			// dx 水平方向变化量
			super.onViewPositionChanged(changedView, left, top, dx, dy);
			Log.d(TAG, "onViewPositionChanged: " + "left: " + left + " dx: " + dx);
			
			int newLeft = left;
			if(changedView == mLeftContent){
				// 把当前变化量传递给mMainContent
				newLeft = mMainContent.getLeft() + dx;
			}
			
			// 进行修正,限定范围
			newLeft = fixLeft(newLeft);
			
			if(changedView == mLeftContent) {
				// 当左面板移动之后, 再强制放回去.
				mLeftContent.layout(0, 0, 0 + mWidth, 0 + mHeight);
				mMainContent.layout(newLeft, 0, newLeft + mWidth, 0 + mHeight);
			}
			// 更新状态,执行动画
			dispatchDragEvent(newLeft);
			
			// 为了兼容低版本, 每次修改值之后, 进行重绘
			invalidate();
		}

(2)更新状态,执行动画dispatchDragEvent(newLeft)---自定义方法
     dispatchDragEvent
    protected void dispatchDragEvent(int newLeft) {
		float percent = newLeft * 1.0f/ mRange;  //获得移动百分比
		//0.0f -> 1.0f
		Log.d(TAG, "percent: " + percent);

       //		* 伴随动画:
		animViews(percent);
		
	}

   animViews()
	private void animViews(float percent) {
		//		> 1. 左面板: 缩放动画, 平移动画, 透明度动画
					// 缩放动画 0.0 -> 1.0 >>> 0.5f -> 1.0f  >>> 0.5f * percent + 0.5f
			//		mLeftContent.setScaleX(0.5f + 0.5f * percent);
			//		mLeftContent.setScaleY(0.5f + 0.5f * percent);
					ViewHelper.setScaleX(mLeftContent, evaluate(percent, 0.5f, 1.0f));//缩放动画,从中间向外放大,使用了类型估值器,跟下面效果一样
					ViewHelper.setScaleY(mLeftContent, 0.5f + 0.5f * percent);//缩放动画,从中间向外放大
					// 平移动画: -mWidth / 2.0f -> 0.0f-----------左布局从父屏幕的一半
					ViewHelper.setTranslationX(mLeftContent, evaluate(percent, -mWidth / 2.0f, 0));
					// 透明度: 0.5 -> 1.0f
					ViewHelper.setAlpha(mLeftContent, evaluate(percent, 0.5f, 1.0f));
				
		//		> 2. 主面板: 缩放动画
					// 1.0f -> 0.8f
					ViewHelper.setScaleX(mMainContent, evaluate(percent, 1.0f, 0.8f));
					ViewHelper.setScaleY(mMainContent, evaluate(percent, 1.0f, 0.8f));
					
		//		> 3. 背景动画: 亮度变化 (颜色变化)
					getBackground().setColorFilter((Integer)evaluateColor(percent, Color.BLACK, Color.TRANSPARENT), Mode.SRC_OVER);
	}

   估值器evaluate()
	    /**
     * 估值器
     * @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);
    }

     颜色变化过度evaluateColor()---------直接用
 
	/**
     * 颜色变化过度
     * @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))));
    }

注意://ViewHelper.setScaleX()代替mLeftContent.setScaleX:可以兼容低版本  需导入nineoldandroids-jar包


5.侧滑面板_状态回调

1.状态监听,三种状态,用枚举值列出
    关闭,打开,拖拽
	/**
	 * 状态枚举
	 */
	public static enum Status {
		Close, Open, Draging;
	}

2.状态监听回调,回调接口,监听不同状态
(1)设置回调接口
	public interface OnDragStatusChangeListener{
		void onClose();
		void onOpen();
		void onDraging(float percent);
	}
 
	public void setDragStatusListener(OnDragStatusChangeListener mListener){
		this.mListener = mListener;
	}

(2)回调方法的执行位置
	protected void dispatchDragEvent(int newLeft) {
		float percent = newLeft * 1.0f/ mRange;
		//0.0f -> 1.0f
		Log.d(TAG, "percent: " + percent);
		
		if(mListener != null){//滑动的回调
			mListener.onDraging(percent);
		}
		
		// 更新状态, 执行回调
		Status preStatus = mStatus;//记录上次状态
		mStatus = updateStatus(percent);
		if(mStatus != preStatus){//如果当前状态不等于上次状态
			// 状态发生变化
			if(mStatus == Status.Close){
				// 当前变为关闭状态
				if(mListener != null){
					mListener.onClose();
				}
			}else if (mStatus == Status.Open) {
				if(mListener != null){
					mListener.onOpen();
				}
			}
		}
		
//		* 伴随动画:
		animViews(percent);
		
	}

    更新状态
    //根据百分比更新状态
	private Status updateStatus(float percent) {
		if(percent == 0f){
			return Status.Close;
		}else if (percent == 1.0f) {
			return Status.Open;
		}
		return Status.Draging;
	}

**********(3)MainActivity中使用监听回调
		// 查找Draglayout, 设置监听
		DragLayout mDragLayout = (DragLayout) findViewById(R.id.dl);
 
		
		mDragLayout.setDragStatusListener(new OnDragStatusChangeListener() {
			
			@Override
			public void onOpen() {
				Utils.showToast(MainActivity.this, "onOpen");
				 
				
			}
			
			@Override
			public void onDraging(float percent) {
				Log.d(TAG, "onDraging: " + percent);// 0 -> 1
			 
			}
			
			@Override
			public void onClose() {
				Utils.showToast(MainActivity.this, "onClose");
			 
			}
		});


3.填充布局----
    MainActivity
public class MainActivity extends Activity {

	private static final String TAG = "TAG";

	@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);
		final ListView mMainList = (ListView) findViewById(R.id.lv_main);
		final ImageView mHeaderImage = (ImageView) findViewById(R.id.iv_header);
		MyLinearLayout mLinearLayout = (MyLinearLayout) findViewById(R.id.mll);
		
		// 查找Draglayout, 设置监听
		DragLayout mDragLayout = (DragLayout) findViewById(R.id.dl);

		// 设置引用
		mLinearLayout.setDraglayout(mDragLayout);
		
		mDragLayout.setDragStatusListener(new OnDragStatusChangeListener() {
			
			@Override
			public void onOpen() {
				Utils.showToast(MainActivity.this, "onOpen");
				// 左面板ListView随机设置一个条目
				Random random = new Random();
				
				int nextInt = random.nextInt(50);
				mLeftList.smoothScrollToPosition(nextInt);
				
			}
			
			@Override
			public void onDraging(float percent) {
				Log.d(TAG, "onDraging: " + percent);// 0 -> 1
				// 更新图标的透明度
				// 1.0 -> 0.0
				ViewHelper.setAlpha(mHeaderImage, 1 - percent);
			}
			
			@Override
			public void onClose() {
				Utils.showToast(MainActivity.this, "onClose");
				// 让图标晃动
//				mHeaderImage.setTranslationX(translationX)
				ObjectAnimator mAnim = ObjectAnimator.ofFloat(mHeaderImage, "translationX", 15.0f);
				mAnim.setInterpolator(new CycleInterpolator(4));
				mAnim.setDuration(500);
				mAnim.start();
			}
		});
		
		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 mText = ((TextView)view);
				mText.setTextColor(Color.WHITE);
				return view;
			}
		});
		
		mMainList.setAdapter(new ArrayAdapter<String>(this, android.R.layout.simple_list_item_1, Cheeses.NAMES));
		
		
		
	}

}



 

6.侧滑面板_触摸优化

1.让主面板在打开或滑动时ListView不能滑动,滑动主面板ListView时关闭,自定义一个LiearLayout

(1)在DragLayout中设置get,set方法设置状态,把DragLayout对象传入LiearLayout中

       MyLinearLayout

public class MyLinearLayout extends LinearLayout {

	private DragLayout mDragLayout;

	public MyLinearLayout(Context context) {
		super(context);
	}

	public MyLinearLayout(Context context, AttributeSet attrs) {
		super(context, attrs);
	}
	
	public void setDraglayout(DragLayout mDragLayout){
		this.mDragLayout = mDragLayout;
	}
	
	@Override
	public boolean onInterceptTouchEvent(MotionEvent ev) {
		// 如果当前是关闭状态, 按之前方法判断
		if(mDragLayout.getStatus() == Status.Close){
			return super.onInterceptTouchEvent(ev);
		}else {
			return true;
		}
	}
	
	@Override
	public boolean onTouchEvent(MotionEvent event) {
		// 如果当前是关闭状态, 按之前方法处理
		if(mDragLayout.getStatus() == Status.Close){
			return super.onTouchEvent(event);
		}else {
			// 手指抬起, 执行关闭操作
			if(event.getAction() == MotionEvent.ACTION_UP){
				mDragLayout.close();
			}
			
			return true;
		}
	}

}


 


 

 

7.DragLayout完整代码

/**
 * 侧滑面板
 * @author poplar
 *
 */
public class DragLayout extends FrameLayout {

	private static final String TAG = "TAG";
	private ViewDragHelper mDragHelper;
	private ViewGroup mLeftContent;
	private ViewGroup mMainContent;
	private OnDragStatusChangeListener mListener;
	private Status mStatus = Status.Close;
	
	/**
	 * 状态枚举
	 */
	public static enum Status {
		Close, Open, Draging;
	}
	public interface OnDragStatusChangeListener{
		void onClose();
		void onOpen();
		void onDraging(float percent);
	}
	
	public Status getStatus() {
		return mStatus;
	}

	public void setStatus(Status mStatus) {
		this.mStatus = mStatus;
	}

	public void setDragStatusListener(OnDragStatusChangeListener 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);
		
		// a.初始化 (通过静态方法) 
		mDragHelper = ViewDragHelper.create(this , mCallback);
		
	}
	
	ViewDragHelper.Callback mCallback = new ViewDragHelper.Callback() {
		// c. 重写事件
		
		// 1. 根据返回结果决定当前child是否可以拖拽
		// child 当前被拖拽的View
		// pointerId 区分多点触摸的id
		@Override
		public boolean tryCaptureView(View child, int pointerId) {
			Log.d(TAG, "tryCaptureView: " + child);
			return true;
		};
		
		@Override
		public void onViewCaptured(View capturedChild, int activePointerId) {
			Log.d(TAG, "onViewCaptured: " + capturedChild);
			// 当capturedChild被捕获时,调用.
			super.onViewCaptured(capturedChild, activePointerId);
		}

		@Override
		public int getViewHorizontalDragRange(View child) {
			// 返回拖拽的范围, 不对拖拽进行真正的限制. 仅仅决定了动画执行速度
			return mRange;
		}
		
		// 2. 根据建议值 修正将要移动到的(横向)位置   (重要)
		// 此时没有发生真正的移动
		public int clampViewPositionHorizontal(View child, int left, int dx) {
			// child: 当前拖拽的View
			// left 新的位置的建议值, dx 位置变化量
			// left = oldLeft + dx;
			Log.d(TAG, "clampViewPositionHorizontal: " 
					+ "oldLeft: " + child.getLeft() + " dx: " + dx + " left: " +left);
			
			if(child == mMainContent){
				left = fixLeft(left);
			}
			return left;
		}

		// 3. 当View位置改变的时候, 处理要做的事情 (更新状态, 伴随动画, 重绘界面)
		// 此时,View已经发生了位置的改变
		@Override
		public void onViewPositionChanged(View changedView, int left, int top,
				int dx, int dy) {
			// changedView 改变位置的View
			// left 新的左边值
			// dx 水平方向变化量
			super.onViewPositionChanged(changedView, left, top, dx, dy);
			Log.d(TAG, "onViewPositionChanged: " + "left: " + left + " dx: " + dx);
			
			int newLeft = left;
			if(changedView == mLeftContent){
				// 把当前变化量传递给mMainContent
				newLeft = mMainContent.getLeft() + dx;
			}
			
			// 进行修正,限定范围
			newLeft = fixLeft(newLeft);
			
			if(changedView == mLeftContent) {
				// 当左面板移动之后, 再强制放回去.
				mLeftContent.layout(0, 0, 0 + mWidth, 0 + mHeight);
				mMainContent.layout(newLeft, 0, newLeft + mWidth, 0 + mHeight);
			}
			// 更新状态,执行动画
			dispatchDragEvent(newLeft);
			
			// 为了兼容低版本, 每次修改值之后, 进行重绘
			invalidate();
		}

		// 4. 当View被释放的时候, 处理的事情(执行动画)
		@Override
		public void onViewReleased(View releasedChild, float xvel, float yvel) {
			// View releasedChild 被释放的子View 
			// float xvel 水平方向的速度, 向右为+
			// float yvel 竖直方向的速度, 向下为+
			Log.d(TAG, "onViewReleased: " + "xvel: " + xvel + " yvel: " + yvel);
			super.onViewReleased(releasedChild, xvel, yvel);
			
			// 判断执行 关闭/开启
			// 先考虑所有开启的情况,剩下的就都是关闭的情况
			if(xvel == 0 && mMainContent.getLeft() > mRange / 2.0f){
				open();//打开超过一半是停止自动打开
			}else if (xvel > 0) {//大于0即方向向右
				open();
			}else {
				close();
			}
			
		}
         
		@Override
		public void onViewDragStateChanged(int state) {
			// TODO Auto-generated method stub
			super.onViewDragStateChanged(state);
		}

	};
	
	/**
	 * 根据范围修正左边值
	 * @param left
	 * @return
	 */
	private int fixLeft(int left) {
		if(left < 0){
			return 0;
		}else if (left > mRange) {
			return mRange;
		}
		return left;
	}
	
	protected void dispatchDragEvent(int newLeft) {
		float percent = newLeft * 1.0f/ mRange;
		//0.0f -> 1.0f
		Log.d(TAG, "percent: " + percent);
		
		if(mListener != null){
			mListener.onDraging(percent);
		}
		
		// 更新状态, 执行回调
		Status preStatus = mStatus;
		mStatus = updateStatus(percent);
		if(mStatus != preStatus){
			// 状态发生变化
			if(mStatus == Status.Close){
				// 当前变为关闭状态
				if(mListener != null){
					mListener.onClose();
				}
			}else if (mStatus == Status.Open) {
				if(mListener != null){
					mListener.onOpen();
				}
			}
		}
		
//		* 伴随动画:
		animViews(percent);
		
	}

	private Status updateStatus(float percent) {
		if(percent == 0f){
			return Status.Close;
		}else if (percent == 1.0f) {
			return Status.Open;
		}
		return Status.Draging;
	}

	private void animViews(float percent) {
		//		> 1. 左面板: 缩放动画, 平移动画, 透明度动画
					// 缩放动画 0.0 -> 1.0 >>> 0.5f -> 1.0f  >>> 0.5f * percent + 0.5f
			//		mLeftContent.setScaleX(0.5f + 0.5f * percent);
			//		mLeftContent.setScaleY(0.5f + 0.5f * percent);
					ViewHelper.setScaleX(mLeftContent, evaluate(percent, 0.5f, 1.0f));
					ViewHelper.setScaleY(mLeftContent, 0.5f + 0.5f * percent);
					// 平移动画: -mWidth / 2.0f -> 0.0f
					ViewHelper.setTranslationX(mLeftContent, evaluate(percent, -mWidth / 2.0f, 0));
					// 透明度: 0.5 -> 1.0f
					ViewHelper.setAlpha(mLeftContent, evaluate(percent, 0.5f, 1.0f));
				
		//		> 2. 主面板: 缩放动画
					// 1.0f -> 0.8f
					ViewHelper.setScaleX(mMainContent, evaluate(percent, 1.0f, 0.8f));
					ViewHelper.setScaleY(mMainContent, evaluate(percent, 1.0f, 0.8f));
					
		//		> 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))));
    }

	@Override
	public void computeScroll() {
		super.computeScroll();
		
		// 2. 持续平滑动画 (高频率调用)
		if(mDragHelper.continueSettling(true)){
			//  如果返回true, 动画还需要继续执行
			ViewCompat.postInvalidateOnAnimation(this);
		}
	}
	
	public void close(){
		close(true);
	}
	/**
	 * 关闭
	 */
	public void close(boolean isSmooth) {
		int finalLeft = 0;
		if(isSmooth){
			// 1. 触发一个平滑动画
			if(mDragHelper.smoothSlideViewTo(mMainContent, finalLeft, 0)){
				// 返回true代表还没有移动到指定位置, 需要刷新界面.
				// 参数传this(child所在的ViewGroup)
				ViewCompat.postInvalidateOnAnimation(this);
			}
		}else {
			mMainContent.layout(finalLeft, 0, finalLeft + mWidth, 0 + mHeight);
		}
	}
	
	public void open(){
		open(true);
	}
	/**
	 * 开启
	 */
	public void open(boolean isSmooth) {
		int finalLeft = mRange;
		if(isSmooth){
			// 1. 触发一个平滑动画
			if(mDragHelper.smoothSlideViewTo(mMainContent, finalLeft, 0)){
				// 返回true代表还没有移动到指定位置, 需要刷新界面.
				// 参数传this(child所在的ViewGroup)
				ViewCompat.postInvalidateOnAnimation(this);
			}
		}else {
			mMainContent.layout(finalLeft, 0, finalLeft + mWidth, 0 + mHeight);
		}
	}

	private int mHeight;
	private int mWidth;
	private int mRange;
	
	// b.传递触摸事件
	@Override
	public boolean onInterceptTouchEvent(MotionEvent ev) {
		// 传递给mDragHelper
		return mDragHelper.shouldInterceptTouchEvent(ev);
	}
	@Override
	public boolean onTouchEvent(MotionEvent event) {
		try {
			mDragHelper.processTouchEvent(event);
		} catch (Exception e) {
			e.printStackTrace();
		}
		// 返回true, 持续接受事件
		return true;
	}
	
	@Override
	protected void onFinishInflate() {
		super.onFinishInflate();
		// Github
		// 写注释
		// 容错性检查 (至少有俩子View, 子View必须是ViewGroup的子类)
		
		if(getChildCount() < 2){
			throw new IllegalStateException("布局至少有俩孩子. Your ViewGroup must have 2 children at least.");
		}
		if(!(getChildAt(0) instanceof ViewGroup && getChildAt(1) instanceof ViewGroup)){
			throw new IllegalArgumentException("子View必须是ViewGroup的子类. Your children must be an instance of ViewGroup");
		}
		
		mLeftContent = (ViewGroup) getChildAt(0);
		mMainContent = (ViewGroup) getChildAt(1);
	}
	
	@Override
	protected void onSizeChanged(int w, int h, int oldw, int oldh) {
		super.onSizeChanged(w, h, oldw, oldh);
		// 当尺寸有变化的时候调用
		
		mHeight = getMeasuredHeight();  //屏幕的高度
		mWidth = getMeasuredWidth();
		
		// 移动的范围
		mRange = (int) (mWidth * 0.6f);
		
	}
	
}


 



 

 



评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值