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