Android之仿美拍主要菜單滑動反彈效果


本文主要記錄一些零碎的東西

先說說要實現的效果:

菜單滑動最左邊,還可以拖動一定距離,松開手后,view自動反彈會原位置

主要的坑:控制滑動的view響應touch事件,里面的子view無法響應click事件

左右滑動很多可以實現,最簡單是 HorizontalScrollView,下面這個布局就可以實現上下左右滑動

<!-- 上下左右滑動 -->
<ScrollView
android:layout_width="match_parent"
android:layout_height="match_parent">

<HorizontalScrollView
android:layout_width="match_parent"
android:layout_height="match_parent">

<View
android:layout_width="wrap_content"
android:layout_height="wrap_content"/>

</HorizontalScrollView>

</ScrollView>

我一開始是直接在activity里監聽view的touch事件,然后,遇到了上面的坑


自定義一個view,處理touch事件,View的移動使用

import android.content.Context;
import android.os.Build;
import android.util.AttributeSet;
import android.util.Log;
import android.view.MotionEvent;
import android.view.View;
import android.widget.HorizontalScrollView;
import android.widget.Scroller;


/**
* <p>Description: 滑動到邊界,反彈效果的 ScrollView </p>
* Created by slack on 2016/10/18 18:37 .
*/
public class BoundScrollView extends HorizontalScrollView {

private static final String TAG = "BoundScrollView";

private static final int DEFAULT_MOVE = 30; // 每次移動距離
private static final long DELAY_DURATION = 10L; // 默認延時時間

private View innerView;// 子View
private int downX, tempX, moveX;
private boolean isFirstTouch;

public BoundScrollView(Context context) {
this(context, null);
}

public BoundScrollView(Context context, AttributeSet attrs) {
this(context, attrs, 0);
}

public BoundScrollView(Context context, AttributeSet attrs, int defStyleAttr) {
super(context, attrs, defStyleAttr);
isFirstTouch = true;
// 取消滑動到頂部或底部時邊緣的黃色或藍色底紋
if (Build.VERSION.SDK_INT >= 9) {
this.setOverScrollMode(View.OVER_SCROLL_NEVER);
}
}

@Override
protected void onFinishInflate() {
super.onFinishInflate();
if (getChildCount() > 0) {
innerView = getChildAt(0);
}
}

@Override
public boolean onTouchEvent(MotionEvent ev) {
if (innerView != null) {
handleTouchEvent(ev);
}
return super.onTouchEvent(ev);
}

/*
* 這是因為ACTION_DOWN和子View的OnClick有沖突,如果touch點在有click事件的view上ACTION_DOWN進入不了
* */
private void handleTouchEvent(MotionEvent event) {
int action = event.getAction();
switch (action) {
case MotionEvent.ACTION_DOWN:
// Log.i(TAG, "ACTION_DOWN...");
break;

case MotionEvent.ACTION_MOVE:

if (isFirstTouch) {
downX = (int) event.getX();
moveX = downX;
isFirstTouch = false;
}

if ((tempX = (int) event.getX() - moveX) != 0) {
innerView.scrollBy(-tempX / 2, 0);
// innerView.offsetLeftAndRight(tempX); // 都可以實現隨手指滑動效果
}
moveX = (int) event.getX();

// Log.i(TAG, "ACTION_MOVE..." + tempX+ " , " + innerView.getScrollX());
break;

case MotionEvent.ACTION_UP:
// Log.i(TAG, "ACTION_UP..." + innerView.getScrollX());
isFirstTouch = true;
// 這里需要處理反彈回去
if(innerView.getScrollX() != 0) {
innerView.postDelayed(new BoundTask(-innerView.getScrollX()), DELAY_DURATION);
}
// innerView.scrollBy( -innerView.getScrollX() , 0);
// innerView.offsetLeftAndRight(downX - (int)event.getX() );
break;

default:
break;
}
}


// 按滑動長度 反彈
private class BoundTask implements Runnable{

private int distanceX;

public BoundTask(int distance) {
this.distanceX = distance;
}

@Override
public void run() {
// Log.i(TAG, "BoundTask..." + innerView.getScrollX() + " " + distanceX);
if(distanceX > 0){
if(distanceX > DEFAULT_MOVE) {
innerView.scrollBy(DEFAULT_MOVE, 0);
distanceX -= DEFAULT_MOVE;
}else {
innerView.scrollBy(distanceX, 0);
distanceX = 0;
}
innerView.postDelayed(this,DELAY_DURATION);
}else if(distanceX < 0){
if(distanceX < -DEFAULT_MOVE) {
innerView.scrollBy(-DEFAULT_MOVE, 0);
distanceX += DEFAULT_MOVE;
}else {
innerView.scrollBy(distanceX, 0);
distanceX = 0;
}
innerView.postDelayed(this,DELAY_DURATION);
}
}
}

}

上面的代碼有些問題,反彈動畫是自己寫的,感覺有些死板,調整移動距離和時間也感覺怪怪的

,覺得還是使用Google提供好的吧 Scroller,再自定義一個布局

FrameLayoutView

import android.content.Context;
import android.util.AttributeSet;
import android.widget.FrameLayout;
import android.widget.Scroller;

/**
* <p>Description: innerView 純粹為實現反彈效果 </p>
* Created by slack on 2016/10/19 14:45 .
* * Scroller的基本用法其實還是比較簡單的,主要可以分為以下幾個步驟:
* 1. 創建Scroller的實例
* 2. 調用startScroll()方法來初始化滾動數據並刷新界面
* 3. 重寫computeScroll()方法,並在其內部完成平滑滾動的邏輯
*/
public class FrameLayoutView extends FrameLayout {

private Scroller mScroller;

public FrameLayoutView(Context context) {
this(context,null);
}

public FrameLayoutView(Context context, AttributeSet attrs) {
this(context, attrs,0);
}

public FrameLayoutView(Context context, AttributeSet attrs, int defStyleAttr) {
super(context, attrs, defStyleAttr);
mScroller = new Scroller(context);
}

@Override
public void computeScroll() {
// 重寫computeScroll()方法,並在其內部完成平滑滾動的邏輯
if ( mScroller.computeScrollOffset()) {
scrollTo(mScroller.getCurrX(), mScroller.getCurrY());
invalidate();
}
}


public void startScroll(int startX, int startY, int dx, int dy, int duration){
mScroller.startScroll(startX, startY, dx, dy, duration);
invalidate();
}

}

BoundScrollView
handleTouchEvent 里 case MotionEvent.ACTION_UP:修改
case MotionEvent.ACTION_UP:
// Log.i(TAG, "ACTION_UP..." + innerView.getScrollX());
isFirstTouch = true;
// 這里需要處理反彈回去
if(innerView.getScrollX() != 0) {
//這里使用了 viewGroup.getScrollX() 和 viewGroup.getScrollY() 作為起始坐標,
// ScrollY 和 ScrollX 記錄了使用 scrollBy 進行偏移的量
//所以使用他們就等於是使用了現在的坐標作為起始坐標,
// 目的坐標為他們的負數,就是偏移量為0的位置,也是view在沒有移動之前的位置
innerView.startScroll(innerView.getScrollX(),
innerView.getScrollY(),
-innerView.getScrollX(),
-innerView.getScrollY(),
800);

// innerView.postDelayed(new BoundTask(-innerView.getScrollX()), DELAY_DURATION);
}

// innerView.scrollBy( -innerView.getScrollX() , 0);
// innerView.offsetLeftAndRight(downX - (int)event.getX() );
break;
完整的
import android.content.Context;
import android.os.Build;
import android.util.AttributeSet;
import android.util.Log;
import android.view.MotionEvent;
import android.view.View;
import android.widget.HorizontalScrollView;


/**
* <p>Description: 滑動到邊界,反彈效果的 ScrollView </p>
* Created by slack on 2016/10/18 18:37 .
*/
public class BoundScrollView extends HorizontalScrollView {

private static final String TAG = "BoundScrollView";

private static final int DEFAULT_MOVE = 30; // 每次移動距離
private static final long DELAY_DURATION = 15L; // 默認延時時間

private FrameLayoutView innerView;// 子View
private int downX, tempX, moveX;
private boolean isFirstTouch;


public BoundScrollView(Context context) {
this(context, null);
}

public BoundScrollView(Context context, AttributeSet attrs) {
this(context, attrs, 0);
}

public BoundScrollView(Context context, AttributeSet attrs, int defStyleAttr) {
super(context, attrs, defStyleAttr);
isFirstTouch = true;
// 取消滑動到頂部或底部時邊緣的黃色或藍色底紋
if (Build.VERSION.SDK_INT >= 9) {
this.setOverScrollMode(View.OVER_SCROLL_NEVER);
}
}

@Override
protected void onFinishInflate() {
super.onFinishInflate();
if (getChildCount() > 0) {
innerView = (FrameLayoutView)getChildAt(0);
}
}

@Override
public boolean onTouchEvent(MotionEvent ev) {
if (innerView != null) {
handleTouchEvent(ev);
}
return super.onTouchEvent(ev);
}

/*
* 這是因為ACTION_DOWN和子View的OnClick有沖突,如果touch點在有click事件的view上ACTION_DOWN進入不了
* */
private void handleTouchEvent(MotionEvent event) {
int action = event.getAction();
switch (action) {
case MotionEvent.ACTION_DOWN:
// Log.i(TAG, "ACTION_DOWN...");
break;

case MotionEvent.ACTION_MOVE:

if (isFirstTouch) {
downX = (int) event.getRawX();
moveX = downX;
isFirstTouch = false;
}

if ((tempX = (int) event.getRawX() - moveX) != 0) {
innerView.scrollBy(-tempX / 2, 0);
// innerView.offsetLeftAndRight(tempX); // 都可以實現隨手指滑動效果
}
moveX = (int) event.getX();

// Log.i(TAG, "ACTION_MOVE..." + tempX+ " , " + innerView.getScrollX());
break;

case MotionEvent.ACTION_UP:
// Log.i(TAG, "ACTION_UP..." + innerView.getScrollX());
isFirstTouch = true;
// 這里需要處理反彈回去
if(innerView.getScrollX() != 0) {
//這里使用了 viewGroup.getScrollX() 和 viewGroup.getScrollY() 作為起始坐標,
// ScrollY 和 ScrollX 記錄了使用 scrollBy 進行偏移的量
//所以使用他們就等於是使用了現在的坐標作為起始坐標,
// 目的坐標為他們的負數,就是偏移量為0的位置,也是view在沒有移動之前的位置
innerView.startScroll(innerView.getScrollX(),
innerView.getScrollY(),
-innerView.getScrollX(),
-innerView.getScrollY(),
800);

// innerView.postDelayed(new BoundTask(-innerView.getScrollX()), DELAY_DURATION);
}

// innerView.scrollBy( -innerView.getScrollX() , 0);
// innerView.offsetLeftAndRight(downX - (int)event.getX() );
break;

default:
break;
}
}


// 按滑動長度 反彈 , 自己處理的感覺有些死板
private class BoundTask implements Runnable{

private int distanceX;

public BoundTask(int distance) {
this.distanceX = distance;
}

@Override
public void run() {
// Log.i(TAG, "BoundTask..." + innerView.getScrollX() + " " + distanceX);
if(distanceX > 0){
if(distanceX > DEFAULT_MOVE) {
innerView.scrollBy(DEFAULT_MOVE, 0);
distanceX -= DEFAULT_MOVE;
}else {
innerView.scrollBy(distanceX, 0);
distanceX = 0;
}
innerView.postDelayed(this,DELAY_DURATION);
}else if(distanceX < 0){
if(distanceX < -DEFAULT_MOVE) {
innerView.scrollBy(-DEFAULT_MOVE, 0);
distanceX += DEFAULT_MOVE;
}else {
innerView.scrollBy(distanceX, 0);
distanceX = 0;
}
innerView.postDelayed(this,DELAY_DURATION);
}
}
}

}


然后界面的布局就變成
<com.benqu.wuta.views.BoundScrollView
android:layout_width="match_parent"
android:layout_height="match_parent"
android:layout_gravity="center"
>
<com.benqu.wuta.views.FrameLayoutView
android:layout_width="match_parent"
android:layout_height="match_parent">

// your view....

</com.benqu.wuta.views.FrameLayoutView>

</com.benqu.wuta.views.BoundScrollView>
這樣修改之后,反彈動畫果然舒服多了。

 







注意!

本站转载的文章为个人学习借鉴使用,本站对版权不负任何法律责任。如果侵犯了您的隐私权益,请联系我们删除。



 
粤ICP备14056181号  © 2014-2020 ITdaan.com