主要是ViewDragHelper的使用(ViewDragHelper: Google2013年IO大会提出的, 解决界面控件拖拽移动问题),就是把两个View叠加在一起拖动顶层View时加上一些伴随动画可实现抽屉特效。
效果图:
抽屉特效:
首先是抽屉特效的自定义View,为了方便使用就继承FrameLayout(继承FrameLayout的原因就是省事,因为FrameLayout自动测量和摆放位置了,而且FrameLayout是上下层叠关系,没有位置相对的关系,正是这样方便了使用)
自定义View(抽屉)代码:
public class DragLayout extends FrameLayout {
private String TAG = DragLayout.class.getName();
private ViewDragHelper mDragHelper;
private ViewGroup mLeftContent;
private ViewGroup mMainContent;
private int mWidth;
private int mHeight;
private int mRange;
public DragStatus getStatus() {
return mStatus;
}
public void setStatus(DragStatus mStatus) {
this.mStatus = mStatus;
}
public DragStatus mStatus = DragStatus.Close;
public OnDragStatusChangeListener mDragChange;
public DragLayout(Context context) {
this(context, null);
}
public DragLayout(Context context, AttributeSet attrs) {
this(context, attrs, 0);
}
public DragLayout(Context context, AttributeSet attrs, int defStyleAttr) {
super(context, attrs, defStyleAttr);
// a.初始化操作
//参数2 最小敏感范围, 值越小, 越敏感
mDragHelper = ViewDragHelper.create(this, 0.5f, mCallback);
}
public enum DragStatus {
Close, Open, Draging;
}
public interface OnDragStatusChangeListener {
void onOpen();
void onClose();
void onDraging(float percent);
}
public void setOnDragStatusChangeListener(OnDragStatusChangeListener mDragChange) {
this.mDragChange = mDragChange;
}
ViewDragHelper.Callback mCallback = new ViewDragHelper.Callback() {
// 根据返回结果决定是否可以拖拽
@Override
public boolean tryCaptureView(View child, int pointerId) {
return true;
}
// 2. 根据建议值 修正将要移动到的(横向)位置
@Override
public int clampViewPositionHorizontal(View child, int left, int dx) {
if (child == mMainContent) {
left = fixLeft(left);
}
return left;
}
// 返回拖拽的范围, 不对拖拽进行的限制. 只是动画执行速度
@Override
public int getViewHorizontalDragRange(View child) {
return mRange;
}
// View已经发生了位置的改变,更新状态, 伴随动画, 重绘界面
@Override
public void onViewPositionChanged(View changedView, int left, int top, int dx, int dy) {
super.onViewPositionChanged(changedView, left, top, dx, dy);
int newLeft = left;
if (changedView == mLeftContent) {
newLeft = fixLeft(mMainContent.getLeft() + dx);
mLeftContent.layout(0, 0, mWidth, mHeight);
mMainContent.layout(newLeft, 0, mWidth + newLeft, mHeight);
}
// 更新状态,执行伴随动画
dispatchDragEvent(newLeft);
}
// 4. 当View被释放的时候, 执行平滑动画
@Override
public void onViewReleased(View releasedChild, float xvel, float yvel) {
super.onViewReleased(releasedChild, xvel, yvel);
// 判断执行 关闭/开启
Log.d(TAG, "onViewReleased:" + "xvel" + xvel);
if (xvel == 0 && mMainContent.getLeft() > mRange / 2.0f) {
open();
} else if (xvel > 0) {
open();
} else {
close();
}
}
};
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;
if (mDragChange != null) {
mDragChange.onDraging(percent);
}
// 更新状态, 执行回调
DragStatus preStetus = mStatus;
mStatus = upDateStetus(percent);
if (mStatus != preStetus) {
if (mStatus == DragStatus.Close) {
if (mDragChange != null) {
mDragChange.onClose();
}
} else if (mStatus == DragStatus.Open) {
if (mDragChange != null) {
mDragChange.onOpen();
}
}
}
//伴随动画:
animViews(percent);
}
private DragStatus upDateStetus(float percent) {
if (percent == 0f) {
return DragStatus.Close;
} else if (percent == 1.0f) {
return DragStatus.Open;
}
return DragStatus.Draging;
}
private void animViews(float percent) {
// 缩放动画
mLeftContent.setScaleX(UiUtils.evaluate(percent, 0.5, 1.0f));
mLeftContent.setScaleY(UiUtils.evaluate(percent, 0.5, 1.0f));
// 平移动画:
mLeftContent.setTranslationX(UiUtils.evaluate(percent, -mWidth / 2.0f, 0f));
// 透明度:
mLeftContent.setAlpha(UiUtils.evaluate(percent, 0.5, 1.0f));
// 主界面: 缩放动画
// 1.0f -> 0.8f
mMainContent.setScaleX(UiUtils.evaluate(percent, 1.0, 0.8f));
mMainContent.setScaleY(UiUtils.evaluate(percent, 1.0, 0.8f));
// 背景动画: 亮度变化
getBackground().setColorFilter((Integer) UiUtils.evaluateColor(percent, Color.BLACK, Color.TRANSPARENT), PorterDuff.Mode.SRC_OVER);
}
// 持续平滑动画
@Override
public void computeScroll() {
super.computeScroll();
if (mDragHelper.continueSettling(true)) {
// 如果返回true, 动画还需要继续执行
ViewCompat.postInvalidateOnAnimation(this);
}
}
public void open() {
open(true);
}
public void open(boolean isSmoot) {
int fianlLeft = mRange;
// 触发一个平滑动画
if (isSmoot) {
if (mDragHelper.smoothSlideViewTo(mMainContent, fianlLeft, 0)) {
// 返回true代表还没有移动到指定位置, 需要刷新界面.
// 参数传this(child所在的ViewGroup)
ViewCompat.postInvalidateOnAnimation(this);
}
} else {
mMainContent.layout(fianlLeft, 0, fianlLeft + mWidth, mHeight);
}
}
public void close() {
close(true);
}
public void close(boolean isSmoot) {
int fianlLeft = 0;
if (isSmoot) {
if (mDragHelper.smoothSlideViewTo(mMainContent, fianlLeft, 0)) {
ViewCompat.postInvalidateOnAnimation(this);
}
} else {
mMainContent.layout(fianlLeft, 0, fianlLeft + mWidth, mHeight);
}
}
// 传递触摸事件
@Override
public boolean onInterceptTouchEvent(MotionEvent ev) {
if (SwipeLayout.mStatus == SwipeLayout.SwipeStatus.Close) {
return mDragHelper.shouldInterceptTouchEvent(ev);
}
return false;
}
@Override
public boolean onTouchEvent(MotionEvent event) {
try {
mDragHelper.processTouchEvent(event);
} catch (Exception e) {
e.printStackTrace();
}
return true;
}
//获取当前View的子View
@Override
protected void onFinishInflate() {
super.onFinishInflate();
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);
// 当尺寸有变化的时候调用
mWidth = getMeasuredWidth();
mHeight = getMeasuredHeight();
// 向右滑的范围
mRange = (int) (mWidth * 0.6f);
}
}
MainActivity代码:
public class MainActivity extends AppCompatActivity {
private String TAG = MainActivity.class.getName();
private MyLinearLayout mLayout;
private DragLayout dl_layout;
private ListView mMainList;
private ImageView mHeaderImage;
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
setStatusBar();
dl_layout = (DragLayout) findViewById(R.id.dl_layout);
mMainList = (ListView) findViewById(R.id.lv_main);
mHeaderImage = (ImageView) findViewById(R.id.iv_header);
mLayout = (MyLinearLayout) findViewById(R.id.rl_main);
mLayout.setmDragLayout(dl_layout);
//打开或关闭抽屉的回调
mHeaderImage.setOnClickListener(new View.OnClickListener() {
@Override
public void onClick(View v) {
dl_layout.open();
}
});
dl_layout.setOnDragStatusChangeListener(new DragLayout.OnDragStatusChangeListener() {
@Override
public void onOpen() {
UiUtils.showToast(MainActivity.this,"onOpen");
}
@Override
public void onClose() {
UiUtils.showToast(MainActivity.this, "onClose");
ObjectAnimator mAnim = ObjectAnimator.ofFloat(mHeaderImage, "translationX", 25.0f);
mAnim.setInterpolator(new CycleInterpolator(3));
mAnim.setDuration(500);
mAnim.start();
}
@Override
public void onDraging(float percent) {
// 更新图标的透明度
// 1.0 -> 0.0
mHeaderImage.setAlpha(1 - percent);
}
});
mMainList.setAdapter(new MyAdapter(getApplicationContext()));
}
protected void setStatusBar() {
StatusBarUtil.setTranslucentForImageView(this, 60, mLayout);
}
}
聊天界面联系人左滑功能:
自定义View代码:
public class SwipeLayout extends FrameLayout {
private View operateView;
private View itmeView;
private int mHeight;
private int mWidth;
private int mRange;
private ViewDragHelper mDragHelper;
private OnSwipeLayoutStateChangeListener onStateChangeListener;
public static SwipeStatus mStatus = SwipeStatus.Close;
public SwipeLayout(Context context) {
this(context, null);
}
public SwipeLayout(Context context, AttributeSet attrs) {
this(context, attrs, 0);
}
public SwipeLayout(Context context, AttributeSet attrs, int defStyleAttr) {
super(context, attrs, defStyleAttr);
mDragHelper = ViewDragHelper.create(this, 1f, mCallback);
}
ViewDragHelper.Callback mCallback = new ViewDragHelper.Callback() {
@Override
public boolean tryCaptureView(View child, int pointerId) {
return true;
}
@Override
public int clampViewPositionHorizontal(View child, int left, int dx) {
// 限定移动范围
if (child == itmeView) {
if (left > 0) {
return 0;
} else if (left < -mRange) {
return -mRange;
}
} else if (child == operateView) {
if (left > mWidth) {
return mWidth;
} else if (left < mWidth - mRange) {
return mWidth - mRange;
}
}
return left;
}
@Override
public void onViewPositionChanged(View changedView, int left, int top, int dx, int dy) {
super.onViewPositionChanged(changedView, left, top, dx, dy);
if(changedView == itmeView){
operateView.offsetLeftAndRight(dx);
}else if (changedView == operateView) {
itmeView.offsetLeftAndRight(dx);
}
dispatchSwipeEvent();
}
@Override
public void onViewReleased(View releasedChild, float xvel, float yvel) {
super.onViewReleased(releasedChild, xvel, yvel);
if (xvel == 0 && itmeView.getLeft() < -mRange / 2.0f) {
open();
} else if (xvel < 0) {
open();
} else {
close();
}
}
};
public enum SwipeStatus {
Close, Open, Draging;
}
public interface OnSwipeLayoutStateChangeListener {
void onClose(SwipeLayout mSwipeLayout);
void onOpen(SwipeLayout mSwipeLayout);
void onDraging(SwipeLayout mSwipeLayout);
// 准备关闭
void onStartClose(SwipeLayout mSwipeLayout);
// 准备开启
void onStartOpen(SwipeLayout mSwipeLayout);
}
public void setOnSwipeLayoutStateChangeListener(OnSwipeLayoutStateChangeListener onStateChangeListener) {
this.onStateChangeListener = onStateChangeListener;
}
private void dispatchSwipeEvent() {
if (onStateChangeListener != null) {
onStateChangeListener.onDraging(this);
}
SwipeStatus preStatus = mStatus;
mStatus = upDateStatus();
if (preStatus != mStatus && onStateChangeListener != null) {
if (mStatus == SwipeStatus.Close) {
onStateChangeListener.onClose(this);
} else if (mStatus == SwipeStatus.Open) {
onStateChangeListener.onOpen(this);
} else if (mStatus == SwipeStatus.Draging) {
if(preStatus == SwipeStatus.Close){
onStateChangeListener.onStartOpen(this);
}else if (preStatus == SwipeStatus.Open) {
onStateChangeListener.onStartClose(this);
}
}
}
}
private SwipeStatus upDateStatus() {
int left = itmeView.getLeft();
if (left == 0) {
return SwipeStatus.Close;
} else if (left == -mRange) {
return SwipeStatus.Open;
}
return SwipeStatus.Draging;
}
@Override
public void computeScroll() {
super.computeScroll();
if (mDragHelper.continueSettling(true)) {
ViewCompat.postInvalidateOnAnimation(this);
}
}
public void open() {
open(true);
}
private void open(boolean isSmooth) {
int fianlLeft = -mRange;
if (isSmooth) {
if (mDragHelper.smoothSlideViewTo(itmeView, fianlLeft, 0)) {
ViewCompat.postInvalidateOnAnimation(this);
}
} else {
layoutContent(true);
}
}
public void close() {
close(true);
}
private void close(boolean isSmooth) {
int fianlLeft = 0;
if (isSmooth) {
if (mDragHelper.smoothSlideViewTo(itmeView, fianlLeft, 0)) {
ViewCompat.postInvalidateOnAnimation(this);
}
} else {
layoutContent(false);
}
}
@Override
protected void onLayout(boolean changed, int left, int top, int right, int bottom) {
super.onLayout(changed, left, top, right, bottom);
layoutContent(false);
}
private void layoutContent(boolean isOpen) {
Rect itemRect = computeFrontViewRect(isOpen);
itmeView.layout(itemRect.left, itemRect.top, itemRect.right, itemRect.bottom);
Rect OperateRect = computeOperateViewFront(itemRect);
operateView.layout(OperateRect.left, OperateRect.top, OperateRect.right, OperateRect.bottom);
// 调整顺序, 把itemRect前置
bringChildToFront(itmeView);
}
private Rect computeOperateViewFront(Rect itemRect) {
int left = itemRect.right;
return new Rect(left, 0, left + mRange, mHeight);
}
private Rect computeFrontViewRect(boolean isOpen) {
int left = 0;
if (isOpen) {
left = -mRange;
}
return new Rect(left, 0, mWidth + left, mHeight);
}
@Override
public boolean onInterceptTouchEvent(MotionEvent ev) {
return mDragHelper.shouldInterceptTouchEvent(ev);
}
@Override
public boolean onTouchEvent(MotionEvent event) {
try {
mDragHelper.processTouchEvent(event);
} catch (Exception e) {
e.printStackTrace();
}
return true;
}
@Override
protected void onFinishInflate() {
super.onFinishInflate();
operateView = getChildAt(0);
itmeView = getChildAt(1);
}
@Override
protected void onSizeChanged(int w, int h, int oldw, int oldh) {
super.onSizeChanged(w, h, oldw, oldh);
mHeight = itmeView.getMeasuredHeight();
mWidth = itmeView.getMeasuredWidth();
mRange = operateView.getMeasuredWidth();
}
}
listView的Adaper:
public class MyAdapter extends BaseAdapter {
protected static final String TAG = "TAG";
public MyAdapter(Context context) {
super();
this.context = context;
opendItems = new ArrayList<>();
}
private Context context;
private ArrayList<SwipeLayout> opendItems;
@Override
public int getCount() {
return Cheeses.NAMES.length;
}
@Override
public Object getItem(int position) {
return NAMES[position];
}
@Override
public long getItemId(int position) {
// TODO Auto-generated method stub
return position;
}
@Override
public View getView(int position, View convertView, ViewGroup parent) {
View view = convertView;
if(convertView == null){
view = View.inflate(context, R.layout.item_list, null);
}
ViewHolder mHolder = ViewHolder.getHolder(view);
SwipeLayout sl = (SwipeLayout)view;
sl.setOnSwipeLayoutStateChangeListener(new SwipeLayout.OnSwipeLayoutStateChangeListener() {
@Override
public void onStartOpen(SwipeLayout mSwipeLayout) {
Log.d(TAG, "onStartOpen");
}
@Override
public void onStartClose(SwipeLayout mSwipeLayout) {
Log.d(TAG, "onStartClose");
}
@Override
public void onOpen(SwipeLayout mSwipeLayout) {
Log.d(TAG, "onOpen");
}
@Override
public void onDraging(SwipeLayout mSwipeLayout) {
}
@Override
public void onClose(SwipeLayout mSwipeLayout) {
Log.d(TAG, "onClose");
}
});
return view;
}
static class ViewHolder {
TextView tv_call;
TextView tv_del;
public static ViewHolder getHolder(View view) {
Object tag = view.getTag();
if(tag == null){
ViewHolder viewHolder = new ViewHolder();
viewHolder.tv_call = (TextView)view.findViewById(R.id.tv_call);
viewHolder.tv_del = (TextView)view.findViewById(R.id.tv_del);
tag = viewHolder;
view.setTag(tag);
}
return (ViewHolder)tag;
}
}
}