先看效果:
实现步骤:
外层用ViewPager,最后一个界面不是一般的控件,用到自定义布局
自定义布局中包含两个界面:最后一张图片,及左滑显示的显示,松手后,界面得回到原来的
具体代码:
package qf.com.slideviewgroup;
import android.animation.ObjectAnimator;
import android.animation.ValueAnimator;
import android.content.Context;
import android.support.v4.view.ViewPager;
import android.support.v4.widget.ViewDragHelper;
import android.util.AttributeSet;
import android.util.Log;
import android.view.MotionEvent;
import android.view.View;
import android.view.animation.DecelerateInterpolator;
import android.widget.LinearLayout;
import java.util.ArrayList;
import java.util.List;
/**
* Created by Administrator on 2017/1/1 0001.
*/
public class SlideViewGroup extends LinearLayout implements View.OnTouchListener {
static final int MODE_LEFT = 1;
static final int MODE_RIGHT = 2;
static final int MODE_LEFT_RIGHT = 3;
/**
* 默认模式
*/
int mode = MODE_RIGHT;
static final String TAG = "ytmfdw";
@Override
public boolean onTouch(View v, MotionEvent event) {
return super.onTouchEvent(event);
}
public interface CallBack {
public boolean leftSliding();
public boolean rightSliding();
}
SlideViewGroup.CallBack callback;
private List<View> views = new ArrayList<>();
/**
* 防止没触发clampViewPositionHorizontal滑动事件
*/
boolean isSlide = false;
/**
* 能否回调
*/
boolean canCallBack = false;
/**
* 当前移动的控件序号
*/
int indexView = 0;
/**
* 开始显示的界面
*/
int startView = 0;
/**
* 结束界面
*/
int endView = 0;
/**
* 向左还是向右滑动,left>0向右,left<0向左
*/
private boolean left = true;
private ViewDragHelper dragHelper;
//动画
private ObjectAnimator anim = null;
public SlideViewGroup(Context context) {
this(context, null);
}
public SlideViewGroup(Context context, AttributeSet attrs) {
this(context, attrs, 0);
}
public SlideViewGroup(Context context, AttributeSet attrs, int defStyleAttr) {
super(context, attrs, defStyleAttr);
initViews();
}
public void setSlideLisgener(SlideViewGroup.CallBack cb) {
this.callback = cb;
}
private void initViews() {
//水平方向
setOrientation(HORIZONTAL);
dragHelper = ViewDragHelper.create(this, 0.2f, new ViewDragHelper.Callback() {
@Override
public boolean tryCaptureView(View child, int pointerId) {
L("tryCaptureView~~~~~~~");
indexView = views.indexOf(child);
L("indexView=" + indexView);
if (anim != null && anim.isRunning()) {
anim.cancel();
anim = null;
}
return true;
}
@Override
public int clampViewPositionHorizontal(View child, int left, int dx) {
// L("clampViewPoistionHorizontal::::left=" + left + "dx=" + dx);
isSlide = true;
canCallBack = false;
if (mode != MODE_LEFT) {
if (indexView == startView) {
return left > 0 ? 0 : dx;
}
}
return dx;
}
@Override
public void onViewPositionChanged(View changedView, int left, int top, int dx, int dy) {
super.onViewPositionChanged(changedView, left, top, dx, dy);
// L("onViewPositionChanged=" + left);
if (!isSlide) {
return;
}
LinearLayout.LayoutParams params = (LayoutParams) views.get(0).getLayoutParams();
params.leftMargin += left;
views.get(0).setLayoutParams(params);
isSlide = false;
}
@Override
public void onViewReleased(View releasedChild, float xvel, float yvel) {
// L("onViewREleased~~~~~~~~~~indexView=" + indexView);
//可以再次加了
int start = getL();
int end = 0;
for (int i = 0; i < views.size(); i++) {
if (i == indexView) {
break;
}
end += views.get(i).getMeasuredWidth();
}
if (callback != null) {
if ((mode == MODE_LEFT || mode == MODE_LEFT_RIGHT) &&
canCallBack && indexView == startView) {
if (callback.leftSliding()) {
return;
}
} else if (mode >= MODE_RIGHT && canCallBack && indexView == endView) {
if (callback.rightSliding()) {
return;
}
}
}
L("start=" + start + "end=" + end);
//创建动画
anim = ObjectAnimator.ofInt(SlideViewGroup.this, "L", start, -end);
anim.setDuration(300);
anim.setInterpolator(new DecelerateInterpolator());
anim.start();
}
//解决点击与滑动事件冲突
@Override
public int getViewHorizontalDragRange(View child) {
return child.isClickable() ? child.getWidth() : 0;
}
@Override
public int getViewVerticalDragRange(View child) {
return child.isClickable() ? child.getHeight() : 0;
}
});
}
@Override
protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
super.onMeasure(widthMeasureSpec, heightMeasureSpec);
//解决子控件的宽度为match_parent
for (View view : views) {
LayoutParams params = (LayoutParams) view.getLayoutParams();
int childW = params.width;
if (childW == -1) {
//match_parent
params.width = this.getMeasuredWidth();
view.setLayoutParams(params);
}
}
}
@Override
protected void onFinishInflate() {
super.onFinishInflate();
int count = getChildCount();
for (int i = 0; i < count; i++) {
views.add(getChildAt(i));
getChildAt(i).setOnTouchListener(this);
}
endView = count - 2;
}
boolean intercept = false;
@Override
public boolean onInterceptTouchEvent(MotionEvent ev) {
switch (ev.getAction()) {
case MotionEvent.ACTION_DOWN: {
start = ev.getX();
}
break;
case MotionEvent.ACTION_MOVE: {
if (indexView >= 0 && indexView < views.size()) {
View view = views.get(indexView);
L(view.getClass().getName());
float dx = ev.getX() - start;
if (dx > 0) {
if (indexView >= startView && mode == MODE_LEFT) {
getParent().requestDisallowInterceptTouchEvent(true);
}
//向右滑动
boolean canScroll = canChildScrollRight(view);
L("向右滑动:" + canScroll);
if (!canScroll) {
L("向右滑动:拦截");
intercept = true;
return intercept;
} else {
return dragHelper.shouldInterceptTouchEvent(ev);
}
} else {
if (indexView <= endView) {
getParent().requestDisallowInterceptTouchEvent(true);
}
//向左滑动
boolean canScroll = canChildScrollLeft(view);
L("向左滑动:" + canScroll);
if (!canScroll) {
L("向左滑动:拦截");
intercept = true;
return intercept;
} else {
return dragHelper.shouldInterceptTouchEvent(ev);
}
}
}
}
break;
case MotionEvent.ACTION_UP: {
if (intercept) {
return true;
}
}
break;
}
return dragHelper.shouldInterceptTouchEvent(ev);
}
float start = 0;
@Override
public boolean onTouchEvent(MotionEvent event) {
switch (event.getAction()) {
case MotionEvent.ACTION_DOWN: {
//这个方法不会被回调
start = event.getX();
}
break;
case MotionEvent.ACTION_UP: {
float dx = event.getX() - start;
L("onTouchEvent up=" + dx);
int w = views.get(indexView).getMeasuredWidth();
if (dx >= w / 2) {
//向右滑动--
indexView--;
if (indexView < startView) {
indexView = startView;
//防止非最后一个被拖动时触发回调
canCallBack = true;
}
} else if (dx <= -w / 2) {
//向左滑动,++
indexView++;
if (indexView > endView) {
indexView = endView;
//防止非最后一个被拖动时触发回调
canCallBack = true;
}
}
}
break;
}
dragHelper.processTouchEvent(event);
return true;
}
public void setL(int left) {
LinearLayout.LayoutParams params = (LayoutParams) views.get(0).getLayoutParams();
params.leftMargin = left;
views.get(0).setLayoutParams(params);
}
public int getL() {
LinearLayout.LayoutParams params = (LayoutParams) views.get(0).getLayoutParams();
return params.leftMargin;
}
public static void L(String msg) {
Log.d(TAG, msg);
}
/**
* 判断子控件是否可以左滑
*
* @param view
* @return
*/
public static boolean canChildScrollLeft(View view) {
L("canChildScrollLeft class =" + view.getClass().getName());
/* if (view instanceof ViewPager) {
final ViewPager viewPager = (ViewPager) view;
return viewPager.getCurrentItem() != 0;
} else {*/
/* if (android.os.Build.VERSION.SDK_INT < 14) {
return view.getScrollX() < 0;
} else {
return view.canScrollHorizontally(1);
}*/
return view.canScrollHorizontally(1);
// }
}
/**
* 判断子控件是否可以左滑
*
* @param view
* @return
*/
public static boolean canChildScrollRight(View view) {
L("canChildScrollRight class =" + view.getClass().getName());
/* if (view instanceof ViewPager) {
final ViewPager viewPager = (ViewPager) view;
return viewPager.getCurrentItem() != viewPager.getAdapter().getCount() - 1;
} else {*/
/*if (android.os.Build.VERSION.SDK_INT < 14) {
return view.getScrollX() > 0;
} else {
return view.canScrollHorizontally(-1);
}*/
return view.canScrollHorizontally(-1);
// }
}
}
需要的技术:
1、ViewDragHelper类,实现布局中的子控件拖动事件
2、属性动画,当手指离开屏幕时,得恢复到某个界面
3、TouchEvent拦截机制
ViewDragHelper解决子控件拖动事件:
在初始化ViewDragHelper中,写个CallBack(注意:这不是接口,是一个抽象类)
相关方法:
clampViewPositionHorizontal 水平方向移动距离,该方法返回的值,给onViewPositionChanged使用
onViewPositionChanged 子控件移动时的位置改变,在这个方法中,设置第一个子控件的margin_left值,就可以达到水平方向滚动效果(前提是该类继承自LinearLayout)
onViewReleased 当手指离开界面时回调,在这个方法中,要判断是否达到触发左右滑动事件(即在最后一个界面时,再向左滑动一半,触发左滑事件),并启动属性动画,让界面恢复到显示某个界面
getViewHorizontalDragRange 解决点击事件冲突
属性动画:
写一个setL和getL的方法,这个方法就是用来设置和获取第一个子控件的margin_left值
然后就可以利用ObjectAnimtor.ofInt(this,"L",startValue,endValue)来创建属性动画了
TouchEvent拦截:
在onInterceptTouchEvent方法中,如果当前正在移动,如果移动方向还是向左的(利用按下时,得到起点x值,再用当前值-起点值,如果<0,左,如果>0右),但当前被拖动的直接子控件不是第一个子控件,就让父控件不要拦截这个事件:
float dx = ev.getX() - start;
if (dx > 0) {
if (indexView >= startView && mode == MODE_LEFT) {
getParent().requestDisallowInterceptTouchEvent(true);
}
向右方向同理
难解决的是事件冲突,这个自定义布局可包含多个直接子控件,在该布局下,停止状态时,同一时刻只能显示一个直接子控件,滑动时可像ViewPager一样
即该布局可左右滑动,ViewPager也可左右滑动,
源码下载:请点击我