ViewDragHelper實(shí)現(xiàn)QQ側(cè)滑效果
前言
側(cè)滑的實(shí)現(xiàn)方式有很多方式來實(shí)現(xiàn),這次總結(jié)的ViewDragHelper就是其中一種方式,ViewDragHelper是2013年谷歌I/O大會發(fā)布的新的控件,為了解決界面控件拖拽問題。下面就是自己學(xué)習(xí)寫的一個實(shí)現(xiàn)類似于QQ側(cè)滑效果的實(shí)現(xiàn)。
activity_main.xml:
<com.yctc.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" >
</ListView>
</LinearLayout>
<com.yctc.drag.MyLinearLayout
android:id="@+id/mll"
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="#18B6EF"
android:gravity="center_vertical" >
<ImageView
android:id="@+id/iv_header"
android:layout_width="30dp"
android:layout_height="30dp"
android:layout_marginLeft="15dp"
android:src="@drawable/head" />
<TextView
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_centerHorizontal="true"
android:text="Header" />
</RelativeLayout>
<ListView
android:id="@+id/lv_main"
android:layout_width="match_parent"
android:layout_height="match_parent" >
</ListView>
</com.yctc.drag.MyLinearLayout>
</com.yctc.drag.DragLayout>
DragLayout.Java:
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;
/**
* 狀態(tài)枚舉
*/
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.初始化 (通過靜態(tài)方法)
mDragHelper = ViewDragHelper.create(this , mCallback);
}
ViewDragHelper.Callback mCallback = new ViewDragHelper.Callback() {
// c. 重寫事件
// 1. 根據(jù)返回結(jié)果決定當(dāng)前child是否可以拖拽
// child 當(dāng)前被拖拽的View
// pointerId 區(qū)分多點(diǎn)觸摸的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);
// 當(dāng)capturedChild被捕獲時,調(diào)用.
super.onViewCaptured(capturedChild, activePointerId);
}
@Override
public int getViewHorizontalDragRange(View child) {
// 返回拖拽的范圍, 不對拖拽進(jìn)行真正的限制. 僅僅決定了動畫執(zhí)行速度
return mRange;
}
// 2. 根據(jù)建議值 修正將要移動到的(橫向)位置 (重要)
// 此時沒有發(fā)生真正的移動
public int clampViewPositionHorizontal(View child, int left, int dx) {
// child: 當(dāng)前拖拽的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. 當(dāng)View位置改變的時候, 處理要做的事情 (更新狀態(tài), 伴隨動畫, 重繪界面)
// 此時,View已經(jīng)發(fā)生了位置的改變
@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){
// 把當(dāng)前變化量傳遞給mMainContent
newLeft = mMainContent.getLeft() + dx;
}
// 進(jìn)行修正
newLeft = fixLeft(newLeft);
if(changedView == mLeftContent) {
// 當(dāng)左面板移動之后, 再強(qiáng)制放回去.
mLeftContent.layout(0, 0, 0 + mWidth, 0 + mHeight);
mMainContent.layout(newLeft, 0, newLeft + mWidth, 0 + mHeight);
}
// 更新狀態(tài),執(zhí)行動畫
dispatchDragEvent(newLeft);
// 為了兼容低版本, 每次修改值之后, 進(jìn)行重繪
invalidate();
}
// 4. 當(dāng)View被釋放的時候, 處理的事情(執(zhí)行動畫)
@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);
// 判斷執(zhí)行 關(guān)閉/開啟
// 先考慮所有開啟的情況,剩下的就都是關(guān)閉的情況
if(xvel == 0 && mMainContent.getLeft() > mRange / 2.0f){
open();
}else if (xvel > 0) {
open();
}else {
close();
}
}
@Override
public void onViewDragStateChanged(int state) {
// TODO Auto-generated method stub
super.onViewDragStateChanged(state);
}
};
/**
* 根據(jù)范圍修正左邊值
* @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);
}
// 更新狀態(tài), 執(zhí)行回調(diào)
Status preStatus = mStatus;
mStatus = updateStatus(percent);
if(mStatus != preStatus){
// 狀態(tài)發(fā)生變化
if(mStatus == Status.Close){
// 當(dāng)前變?yōu)殛P(guān)閉狀態(tài)
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. 持續(xù)平滑動畫 (高頻率調(diào)用)
if(mDragHelper.continueSettling(true)){
// 如果返回true, 動畫還需要繼續(xù)執(zhí)行
ViewCompat.postInvalidateOnAnimation(this);
}
}
public void close(){
close(true);
}
/**
* 關(guān)閉
*/
public void close(boolean isSmooth) {
int finalLeft = 0;
if(isSmooth){
// 1. 觸發(fā)一個平滑動畫
if(mDragHelper.smoothSlideViewTo(mMainContent, finalLeft, 0)){
// 返回true代表還沒有移動到指定位置, 需要刷新界面.
// 參數(shù)傳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. 觸發(fā)一個平滑動畫
if(mDragHelper.smoothSlideViewTo(mMainContent, finalLeft, 0)){
// 返回true代表還沒有移動到指定位置, 需要刷新界面.
// 參數(shù)傳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, 持續(xù)接受事件
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);
// 當(dāng)尺寸有變化的時候調(diào)用
mHeight = getMeasuredHeight();
mWidth = getMeasuredWidth();
// 移動的范圍
mRange = (int) (mWidth * 0.6f);
}
}
MyLineatLayout.java:
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) {
// 如果當(dāng)前是關(guān)閉狀態(tài), 按之前方法判斷
if(mDragLayout.getStatus() == Status.Close){
return super.onInterceptTouchEvent(ev);
}else {
return true;
}
}
@Override
public boolean onTouchEvent(MotionEvent event) {
// 如果當(dāng)前是關(guān)閉狀態(tài), 按之前方法處理
if(mDragLayout.getStatus() == Status.Close){
return super.onTouchEvent(event);
}else {
// 手指抬起, 執(zhí)行關(guān)閉操作
if(event.getAction() == MotionEvent.ACTION_UP){
mDragLayout.close();
}
return true;
}
}
}
MainActivity.java:
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, 設(shè)置監(jiān)聽
DragLayout mDragLayout = (DragLayout) findViewById(R.id.dl);
// 設(shè)置引用
mLinearLayout.setDraglayout(mDragLayout);
mDragLayout.setDragStatusListener(new OnDragStatusChangeListener() {
@Override
public void onOpen() {
Utils.showToast(MainActivity.this, "onOpen");
// 左面板ListView隨機(jī)設(shè)置一個條目
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
// 更新圖標(biāo)的透明度
// 1.0 -> 0.0
ViewHelper.setAlpha(mHeaderImage, 1 - percent);
}
@Override
public void onClose() {
Utils.showToast(MainActivity.this, "onClose");
// 讓圖標(biāo)晃動
// 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))
}
}
以上就是本文的全部內(nèi)容,希望對大家的學(xué)習(xí)有所幫助,也希望大家多多支持腳本之家。
相關(guān)文章
Android中Serializable和Parcelable序列化對象詳解
這篇文章主要介紹了Android中Serializable和Parcelable序列化對象的相關(guān)資料,感興趣的小伙伴們可以參考一下2016-02-02
Android實(shí)現(xiàn)輕量線性與百分比圖表的方法
這篇文章主要給大家介紹了關(guān)于Android實(shí)現(xiàn)輕量線性與百分比圖表的相關(guān)資料,文中通過示例代碼介紹的非常詳細(xì),對大家的學(xué)習(xí)或者工作具有一定的參考學(xué)習(xí)價值,需要的朋友們下面隨著小編來一起學(xué)習(xí)學(xué)習(xí)學(xué)習(xí)吧。2017-12-12
Android 屏幕切換監(jiān)聽的實(shí)例代碼
我試著在屏幕切換時,使View顯示在不同的位置,在網(wǎng)上搜索了一些資料,自己做了一段時間,終于完成了功能,今天小編給大家分享android 屏幕切換監(jiān)聽的實(shí)例代碼,需要的的朋友參考下吧2017-01-01
Android自定義Animation實(shí)現(xiàn)View搖擺效果
這篇文章主要為大家詳細(xì)介紹了Android自定義Animation實(shí)現(xiàn)View搖擺效果,具有一定的參考價值,感興趣的小伙伴們可以參考一下2017-01-01
Android Fragment中使用SurfaceView切換時閃一下黑屏的解決辦法
本篇文章主要給大家分享Android Fragment中使用SurfaceView切換時閃一下黑屏的解決辦法,需要的朋友可以參考下2015-09-09
Android 混合動畫詳解及實(shí)現(xiàn)代碼
這篇文章主要介紹了Android 混合動畫詳解及實(shí)現(xiàn)代碼的相關(guān)資料,簡單的一種動畫(如旋轉(zhuǎn)、縮放、漸變、位移等)有時候并不能滿足我們項(xiàng)目的要求,這時候就需要運(yùn)用到混合動畫,需要的朋友可以參考下2016-11-11
Android實(shí)現(xiàn)一個比相冊更高大上的左右滑動特效(附源碼)
這篇文章主要介紹了Android實(shí)現(xiàn)一個比相冊更高大上的左右滑動特效(附源碼),本文給大家介紹的非常詳細(xì),對大家的學(xué)習(xí)或工作具有一定的參考借鑒價值,需要的朋友可以參考下2021-02-02
Android實(shí)現(xiàn)幾種推送方式解決方案
推送功能在手機(jī)開發(fā)中應(yīng)用的場景是越來起來了,本篇文章主要介紹了Android實(shí)現(xiàn)幾種推送方式解決方案 ,具有一定的參考價值,有興趣的可以了解一下。2016-12-12

