自定义控件
package com.itheima57.draglayout.ui;
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.view.MotionEvent;
import android.view.View;
import android.view.ViewDebug;
import android.view.ViewGroup;
import android.widget.FrameLayout;
/** * 侧滑面板 * @author poplar * */
public class DragLayout extends FrameLayout {
private ViewDragHelper mDragHelper;
private ViewGroup mLeftContent; // 左面板
private ViewGroup mMainContent; // 主面板
private int mHeight;// 控件高度
private int mWidth; //控件宽度
private int mRange; // 横向拖拽范围
public static enum Status {
Close, Open, Draging
}
private Status status = Status.Close;
private OnDragStatusChangeListener mListener;
/** * 状态更新接口 * @author poplar * */
public interface OnDragStatusChangeListener {
void onOpen();
void onClose();
void onDraging(float percent);
}
public void setOnDragStatusChangeListener(OnDragStatusChangeListener mListener){
this.mListener = mListener;
}
// 在代码里 new 对象
public DragLayout(Context context) {
this(context, null);
}
// 布局文件中使用, AttributeSet 布局文件中声明的属性
public DragLayout(Context context, AttributeSet attrs) {
this(context, attrs, 0);
}
public DragLayout(Context context, AttributeSet attrs, int defStyle) {
super(context, attrs, defStyle);
// 1. 初始化拖拽辅助工具, 通过静态方法
// forParent ViewDragHelper生效的布局
// sensitivity 敏感度, 越大越敏感 1.0默认值
// Callback 提供信息, 接受事件
mDragHelper = ViewDragHelper.create(this, 1.0f, mCallback);
}
/** * 3. 重写 Callback父类方法, 接受并处理 ViewDragHelper 解析后的信息 */
ViewDragHelper.Callback mCallback = new ViewDragHelper.Callback() {
// a. 尝试捕获View, 决定了child是否可以被拖拽
// 返回true, 表示child可以被拖拽
@Override
public boolean tryCaptureView(View child, int pointerId) {
// child 被拖拽的子View / ViewGroup
// pointerId 多点触摸的手指id
// System.out.println("child: " + child.toString());
return true;
}
// 当capturedChild被捕获时, 调用
@Override
public void onViewCaptured(View capturedChild, int activePointerId) {
super.onViewCaptured(capturedChild, activePointerId);
}
// b.设置视图横向的拖拽范围, 不限制真正的位置
// 由返回值确定了 动画执行 时长, 以及此方向(水平)是否可以被拖拽
// 大于0 才可以拖拽
@Override
public int getViewHorizontalDragRange(View child) {
return mRange;
}
/** * c. (重要)决定了横向的滑动位置, left 是建议值, 在这里可以进行拖拽位置的修正 * 此时将要发生移动, 还未真正移动 */
@Override
public int clampViewPositionHorizontal(View child, int left, int dx) {
// child 将要发生位移的View
// left 将要移动到的位置, left = oldLeft + dx;
// dx 是变化量
int oldLeft = mMainContent.getLeft();
// System.out.println("clampViewPositionHorizontal: left: " + left + " oldLeft: " + oldLeft + " dx: " + dx);
if(child == mMainContent){
left = fixLeft(left);
}
// 返回值决定了真正要移动到的位置
return left;
}
/** * d. (重要)决定了位置发生改变之后, 要做的事情. 伴随动画, 更新状态, 执行回调 */
@Override
public void onViewPositionChanged(View changedView, int left, int top,
int dx, int dy) {
// changedView : 位置发生改变的View
// left : 最新的横向位置
// top : 最新的纵向位置
// dx : 刚刚发生的横向偏移量
// dy : 刚刚发生的纵向偏移量
System.out.println("onViewPositionChanged: left: " + left + " dx: " + dx);
if(changedView == mLeftContent){
// 发现拖拽的是左面板, 强制放回原位置
mLeftContent.layout(0, 0, mWidth, mHeight);
// 把变化量转交给主面板
int newLeft = mMainContent.getLeft() + dx;
// 再次修正
newLeft = fixLeft(newLeft);
mMainContent.layout(newLeft, 0, newLeft + mWidth, 0 + mHeight);
}
dispatchDragEvent();
invalidate(); // 为了兼容低版本, 需要手动重回界面,使改变的值生效
}
/** * e. 当View被释放时调用此方法, 做动画. */
@Override
public void onViewReleased(View releasedChild, float xvel, float yvel) {
// releasedChild 被释放的孩子
// xvel : 水平方向的速度 向右+ 向左-
// yvel : 垂直方向的速度 向下+ 下上-
// 根据松手时的速度或位置执行动画
if(xvel == 0 && mMainContent.getLeft() > mRange * 0.5f){
// 静止松手, 左面板一半的右边, 打开
open();
} else if (xvel > 0) {
open();
} else {
close();
}
}
@Override
public void onViewDragStateChanged(int state) {
super.onViewDragStateChanged(state);
// * @see #STATE_IDLE // 限制状态
// * @see #STATE_DRAGGING // 拖拽状态
// * @see #STATE_SETTLING // 自动化状态
}
};
/** * 修正边界 * @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 left = mMainContent.getLeft();
// 0.0 -> 1.0
float percent = left * 1.0f / mRange;
System.out.println("percent: " + percent);
// 执行动画
animViews(percent);
// 更新状态, 执行回调
if(mListener != null){
mListener.onDraging(percent);
}
Status preStatus = status;
status = updateStatus(percent);
// 对比最新的状态和刚刚的状态
if(preStatus != status && mListener != null){
// 状态发生了改变
if(status == Status.Close){
mListener.onClose();
}else if (status == Status.Open) {
mListener.onOpen();
}
}
}
/** * 更新状态 * @param percent * @return */
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) {
// - 左面板: 缩放动画, 透明度动画, 平移动画
// 0.5 -> 1.0 >>> percent * 0.5f + 0.5f
// mLeftContent.setScaleX(percent * 0.5f + 0.5f);
// mLeftContent.setScaleY(percent * 0.5f + 0.5f);
// float f = percent * 0.5f + 0.5f;
ViewHelper.setScaleX(mLeftContent, evaluate(percent, 0.5f, 1.0f));
ViewHelper.setScaleY(mLeftContent, evaluate(percent, 0.5f, 1.0f));
// 0.5 -> 1.0 >>> 0.4 ->1.0
ViewHelper.setAlpha(mLeftContent, evaluate(percent, 0.4f, 1.0f));
// 平移动画 -mWidth*0.5f -> 0 // 类型估值器
ViewHelper.setTranslationX(mLeftContent, evaluate(percent, -mWidth*0.5f, 0));
// - 主面板: 缩放动画 // 1.0 -> 0.8
ViewHelper.setScaleX(mMainContent, evaluate(percent, 1.0f, 0.8f));
ViewHelper.setScaleY(mMainContent, evaluate(percent, 1.0f, 0.8f));
// - 背景: 亮度
getBackground().setColorFilter((Integer)evaluateColor(percent, Color.BLACK, Color.TRANSPARENT), Mode.SRC_OVER);
}
/** * 颜色估算器 * @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))));
}
/** * 估值器, 估算一个从开始到结束的中间值 * @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);
}
@Override
public void computeScroll() {
super.computeScroll();
// b. 维持动画的继续
if(mDragHelper.continueSettling(true)){
// true说明动画还没移动到指定位置, 需要继续引发重绘
ViewCompat.postInvalidateOnAnimation(this);
}
}
/** * 关闭 */
protected void close() {
close(true);
}
public void close(boolean isSmooth){
int finalLeft = 0;
if(isSmooth){
// 平滑动画
// a. 触发平滑动画
if(mDragHelper.smoothSlideViewTo(mMainContent, finalLeft, 0)){
// 说明还没移动到最终位置, 需要重绘界面. 参数传控件所在ViewGroup
ViewCompat.postInvalidateOnAnimation(this);
}
}else {
mMainContent.layout(finalLeft, 0,finalLeft + mWidth, mHeight);
}
}
/** * 打开 */
protected void open() {
open(true);
}
public void open(boolean isSmooth){
int finalLeft = mRange;
if(isSmooth){
// 平滑动画
// a. 触发平滑动画
if(mDragHelper.smoothSlideViewTo(mMainContent, finalLeft, 0)){
// 说明还没移动到最终位置, 需要重绘界面. 参数传控件所在ViewGroup
ViewCompat.postInvalidateOnAnimation(this);
}
}else {
mMainContent.layout(finalLeft, 0,finalLeft + mWidth, mHeight);
}
}
/** * 2. 转交触摸事件拦截判断 */
public boolean onInterceptTouchEvent(android.view.MotionEvent ev) {
return mDragHelper.shouldInterceptTouchEvent(ev);
};
@Override
public boolean onTouchEvent(MotionEvent event) {
// 拦截后, 交由mDragHelper处理触摸事件
try {
mDragHelper.processTouchEvent(event);
} catch (Exception e) {
e.printStackTrace();
}
// 由当前控件消费事件
return true;
}
@Override
protected void onSizeChanged(int w, int h, int oldw, int oldh) {
super.onSizeChanged(w, h, oldw, oldh);
// 当尺寸改变时调用.onMeasure之后调用.
mHeight = mMainContent.getMeasuredHeight();
mWidth = mMainContent.getMeasuredWidth();
mRange = (int) (mWidth * 0.6f);
}
@Override
protected void onFinishInflate() {
super.onFinishInflate();
// 当xml完全填充成view对象时,调用
// Github 做一个健壮性检查
// 检查孩子的个数
if(getChildCount() < 2){
throw new IllegalStateException("孩子必须至少有俩, Your layout must contains 2 children at least!");
}
// 检查孩子的类型, 必须是ViewGroup子类
if(!(getChildAt(0) instanceof ViewGroup) || !(getChildAt(1) instanceof ViewGroup)) {
throw new IllegalArgumentException("孩子必须是ViewGroup的子类, Your child must be an instance of ViewGroup");
}
// 获取左面板
mLeftContent = (ViewGroup) getChildAt(0);
// 获取主面板
mMainContent = (ViewGroup) getChildAt(1);
}
}
MainActivity:
package com.itheima57.draglayout;
import java.util.Random;
import android.app.Activity;
import android.graphics.Color;
import android.os.Bundle;
import android.view.View;
import android.view.ViewGroup;
import android.view.Window;
import android.view.animation.CycleInterpolator;
import android.widget.ArrayAdapter;
import android.widget.ImageView;
import android.widget.ListView;
import android.widget.TextView;
import com.itheima57.draglayout.ui.DragLayout;
import com.itheima57.draglayout.ui.DragLayout.OnDragStatusChangeListener;
import com.itheima57.draglayout.util.Cheeses;
import com.itheima57.draglayout.util.Utils;
import com.nineoldandroids.animation.ObjectAnimator;
import com.nineoldandroids.view.ViewHelper;
public class MainActivity extends Activity {
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
requestWindowFeature(Window.FEATURE_NO_TITLE);
setContentView(R.layout.activity_main);
final ImageView iv_header = (ImageView) findViewById(R.id.iv_header);
final ListView lv_left = (ListView) findViewById(R.id.lv_left);
ListView lv_main = (ListView) findViewById(R.id.lv_main);
DragLayout dragLayout = (DragLayout) findViewById(R.id.dl);
dragLayout.setOnDragStatusChangeListener(new OnDragStatusChangeListener() {
@Override
public void onOpen() {
// 打开, 左ListView随机跳动
Utils.showToast(MainActivity.this, "onOpen");
lv_left.smoothScrollToPosition(new Random().nextInt(50));
}
@Override
public void onDraging(float percent) {
// 动态更新小头像的透明度 1.0 -> 0.0
Utils.showToast(MainActivity.this, "onDraging: "+ percent);
ViewHelper.setAlpha(iv_header, 1 - percent);
}
@Override
public void onClose() {
// 小头像执行动画
Utils.showToast(MainActivity.this, "onClose");
// iv_header.setTranslationX(float translationX);
ObjectAnimator mAnim = ObjectAnimator.ofFloat(iv_header, "translationX", 15f);
mAnim.setInterpolator(new CycleInterpolator(4f));
mAnim.setDuration(500);
mAnim.start();
}
});
lv_left.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)view).setTextColor(Color.WHITE);
return view;
}
});
lv_main.setAdapter(new ArrayAdapter<String>(this, android.R.layout.simple_list_item_1, Cheeses.NAMES));
}
}