千言无语抵不过一张图,这图详细说明相关分发事件处理:
或
说到了事件处理,必定少不了相关坐标系
在自定义View的时候,一般都会使用到一些Android系统提供的工具,这些工具封装了一些常用的方法:如拖拽View、计算滑动速度、View滚动、手势处理...。常用系统的封装好的工具
Configuration
Configuration对象获取:Configuration config = getResources().getConfiguration();
Configuration用来描述设备的配置信息
比如用户的配置信息:local和scaling等等
比如设备的相关信息:输入模式、屏幕大小、屏幕方向等等
采用如下方式可以获取相关需要信息:
字段 | |
---|---|
public static finalCreator<Configuration> | CREATOR |
public int | densityDpi 渲染的目标屏幕密度对应于密度资源限定符 |
public float | fontScale 当前用户对字体缩放因子的偏好,相对于基本密度缩放 |
public int | hardKeyboardHidden 指示硬键盘是否已隐藏的标志。 |
public int | keyboard 键盘连接到设备的类型。 |
public int | keyboardHidden 指示是否有任何键盘可用的标志。 |
public Locale | locale 该字段在API级别24.不要设置或直接读取这被废弃了。使用 |
public int | mcc IMSI MCC(移动国家代码),对应于mcc资源限定符。 |
public int | mnc IMSI MNC(移动网络代码),对应于mnc资源限定符 |
public int | navigation 在设备上可用的导航方法的类型。 |
public int | navigationHidden 指示是否有任何5-way或DPAD导航可用的标志 |
public int | orientation 屏幕的总体方向。 |
public int | screenHeightDp 可用屏幕空间的当前高度(以dp为单位),对应于屏幕高度资源限定符。 |
public int | screenLayout 屏幕的整体布局的位掩码。 |
public int | screenWidthDp 可用屏幕空间的当前宽度(以dp为单位),对应于屏幕宽度资源限定符。 |
public int | smallestScreenWidthDp 应用程序在正常操作中将看到的最小屏幕大小,对应于最小屏幕宽度资源限定符。 |
public int | touchscreen 触摸屏的类型连接到设备。 |
public int | uiMode ui模式的位掩码。 |
StringBuilder stringBuilder = new StringBuilder();
//获取Configuration对象
Configuration configuration = getResources().getConfiguration();
//获取国家码
int countryCode = configuration.mcc;
stringBuilder.append("国家码:").append(countryCode + "\n");
//获取网络码
int networkCode = configuration.mnc;
stringBuilder.append("网络码:").append(networkCode + "\n");
//判断横竖屏
if (configuration.orientation == Configuration.ORIENTATION_PORTRAIT){ //竖屏
stringBuilder.append("屏幕竖屏\n");
}else if (configuration.orientation == Configuration.ORIENTATION_LANDSCAPE){ //横屏
stringBuilder.append("屏幕横屏\n");
}
DisplayMetrics metrics = this.getApplicationContext().getResources().getDisplayMetrics();
float density = metrics.density;
stringBuilder.append("屏幕密度:").append(density + "\n");
int densityDpi = configuration.densityDpi;
stringBuilder.append("目标屏幕密度(指的是倍数):").append(densityDpi + "\n");
float fontScale = configuration.fontScale;
stringBuilder.append("字体缩放情况:").append(fontScale + "\n");
需注意 目标屏幕密度(单位像素)= 屏幕密度x160;
为什么要乘以160呢?这就涉及到了手机屏幕密度问题
屏幕密度:单位英寸面积上的像素点数
屏幕物理尺寸 = Math.sqrt(Math.pow(宽,2)+Math.pow(高, 2))/DPI =(((屏幕宽度的分辨率的平方 + 屏幕高度的分辨率的平方)之和 )在开方 )/ (除以手机屏幕的尺寸) = 手机屏幕对角尺寸
ldpi: 屏幕密度为120的手机设备
mdpi: 屏幕密度为160的手机设备(此为baseline,其他均以此为基准,在此设备上,1dp = 1px)
hdpi: 屏幕密度为240的手机设备
xhdpi: 屏幕密度为320的手机设备
xxhdpi:屏幕密度为480的手机设备
这篇描述了Android中屏幕密度和图片大小的关系分析想了解可以看一下
ViewConfiguration
看完Configuration再来瞅ViewConfiguration。这两者的名字有些像,差了一个View;咋一看,还以为它俩是继承关系,其实不然
ViewConfiguration提供了一些自定义控件用到的标准常量,比如尺寸大小,滑动距离,敏感度等等。
ViewConfiguration对象获取:ViewConfiguration viewConfiguration = ViewConfiguration.get(this);
公共方法 | |
---|---|
staticViewConfiguration | get(Context context) |
static long | getDefaultActionModeHideDuration() |
static int | getDoubleTapTimeout() |
static int | getEdgeSlop() This method was deprecated in API level 3. |
static int | getFadingEdgeLength() This method was deprecated in API level 3.使用 |
static long | getGlobalActionKeyTimeout() This method was deprecated in API level 20. This timeout should not be used by applications |
static int | getJumpTapTimeout() |
static int | getKeyRepeatDelay() |
static int | getKeyRepeatTimeout() |
static int | getLongPressTimeout() |
static int | getMaximumDrawingCacheSize() This method was deprecated in API level 3.使用 |
static int | getMaximumFlingVelocity() This method was deprecated in API level 4.使用 |
static int | getMinimumFlingVelocity() This method was deprecated in API level 3.使用 |
static int | getPressedStateDuration() |
int | getScaledDoubleTapSlop() |
int | getScaledEdgeSlop() |
int | getScaledFadingEdgeLength() |
int | getScaledMaximumDrawingCacheSize() 以字节表示的最大冲压高速缓存大小。 |
int | getScaledMaximumFlingVelocity() |
int | getScaledMinimumFlingVelocity() |
int | getScaledOverflingDistance() |
int | getScaledOverscrollDistance() |
int | getScaledPagingTouchSlop() |
int | getScaledScrollBarSize() |
int | getScaledTouchSlop() |
int | getScaledWindowTouchSlop() |
static int | getScrollBarFadeDuration() |
static int | getScrollBarSize() This method was deprecated in API level 3.使用 |
static int | getScrollDefaultDelay() |
static float | getScrollFriction() 施加到卷轴和甩摩擦的量。 |
static int | getTapTimeout() |
static int | getTouchSlop() This method was deprecated in API level 3.使用 |
static int | getWindowTouchSlop() This method was deprecated in API level 3.使用 |
static long | getZoomControlsTimeout() 应在屏幕上显示缩放控件的时间量,以毫秒为单位。 |
boolean | hasPermanentMenuKey() 报告设备是否有永久的菜单键可供用户使用。 |
StringBuilder strBuilder = new StringBuilder();
ViewConfiguration viewConfiguration = ViewConfiguration.get(this);
//获取touchSlope.该值表示系统所能识别出的被认为是滑动的最小距离
int touchSlop = viewConfiguration.getScaledTouchSlop();
strBuilder.append("系统识别最小滑动距离:").append(touchSlop+"\n");
//获取Fling速度的最小值和最大值
int minimumVelocity = viewConfiguration.getScaledMinimumFlingVelocity();
strBuilder.append("Fling速度最小值:").append(minimumVelocity + "\n");
int maximumVelocity = viewConfiguration.getScaledMaximumFlingVelocity();
strBuilder.append("Fling速度最大值:").append(maximumVelocity + "\n");
//判断是否有物理按键
boolean isHavePermanentMenuKey = viewConfiguration.hasPermanentMenuKey();
strBuilder.append("判断是否有物理按键:").append(isHavePermanentMenuKey + "\n");
mTv.setText(strBuilder.toString());
Fling
当用户手指快速划过屏幕,然后快速离开屏幕时,系统会判定用户执行了一个Fling手势。视图会快速滚动,并且在手指离开屏幕之后也会滚动一段时间。Drag表示手指滑动了多少距离,界面跟着显示多少距离,而Fling是根据滑动方向与轻重还会自动滑动一段距离。Fling手势在android交互设计中应该非常广泛:电子书的滑动翻页、ListView滑动删除item、滑动解锁等,Fling就先说这么多先,后面会继续说到。
GestureDetector
官方解释
使用提供的MotionEvent检测各种手势和事件。 GestureDetector.OnGestureListener回调将在特定运动事件发生时通知用户。 此类只应与通过触摸报告的MotionEvent(不用于轨迹球事件)一起使用。
要使用此类:
为您的视图创建GestureDetector的实例
在onTouchEvent(MotionEvent)方法中,确保调用onTouchEvent(MotionEvent)。 在回调中定义的方法将在事件发生时执行。
如果监听onContextClick(MotionEvent),则必须在onGenericMotionEvent(MotionEvent)中调用onGenericMotionEvent(MotionEvent)。
嵌套类 | |
---|---|
interface | GestureDetector.OnContextClickListener 用于在发生上下文单击时通知的侦听器。 |
interface | GestureDetector.OnDoubleTapListener 用于在发生双击或确认单击时通知的侦听器。 |
interface | GestureDetector.OnGestureListener 用于在发生手势时通知的侦听器。 |
class | GestureDetector.SimpleOnGestureListener 当你只想监听所有手势的子集时,扩展的便利类。 |
第一步:实现OnGestureListener
private class GestureListenerImpl implements GestureDetector.OnGestureListener{
//触摸屏幕时均会调用该方法
@Override
public boolean onDown(MotionEvent e) {
return false;
}
//手指在屏幕上按下,且未移动和松开时调用该方法
@Override
public void onShowPress(MotionEvent e) {
}
//轻击屏幕时调用该方法
@Override
public boolean onSingleTapUp(MotionEvent e) {
return false;
}
//手指在屏幕上滚动时会调用该方法
@Override
public boolean onScroll(MotionEvent e1, MotionEvent e2, float distanceX, float distanceY) {
return false;
}
//手指长按屏幕时均会调用该方法
@Override
public void onLongPress(MotionEvent e) {
}
//手指在屏幕上拖动时会调用该方法
@Override
public boolean onFling(MotionEvent e1, MotionEvent e2, float velocityX, float velocityY) {
return false;
}
}
第二步:生成GestureDetector对象
GestureDetector gestureDetector = new GestureDetector(context,new GestureListenerImpl());
第三步:将Touch事件交给GestureDetector处理
@Override
public boolean onTouchEvent(MotionEvent event) {
return gestureDetector.onTouchEvent(event);
}
VelocityTracker(速度追踪器)
官方解释
用于跟踪触摸事件的速度,用于实现拖拽和其他这样的手势。当您要开始跟踪时,使用gets()来检索类的新实例。 使用addMovement(MotionEvent)将接收到的运动事件放入其中。当你想要确定速度调用computeCurrentVelocity(int),然后调用getXVelocity(int)和getYVelocity(int)检索每个指针id的速度。
公共方法 | |
---|---|
void | addMovement(MotionEvent event) 将用户的移动添加到跟踪器。 |
void | clear() 将速度跟踪器重置回其初始状态。 |
void | computeCurrentVelocity(int units) 相当于调用computeCurrentVelocity(int,float),最大速度为Float.MAX_VALUE。 |
void | computeCurrentVelocity(int units, float maxVelocity) 基于已收集的点计算当前速度。 int unitis表示速率的基本时间单位。unitis值为1的表示是,一毫秒时间单位内运动了多少个像素, unitis值为1000表示一秒(1000毫秒)时间单位内运动了多少个像素 |
float | getXVelocity(int id) 检索最后计算的X速度。 |
float | getXVelocity() 检索最后计算的X速度。 |
float | getYVelocity() 检索最后计算的Y速度。 |
float | getYVelocity(int id) 检索最后计算的Y速度。 |
staticVelocityTracker | obtain() 检索一个新的VelocityTracker对象以观察运动的速度。 |
void | recycle() 返回一个VelocityTracker对象,以供其他人重复使用。 |
第一点是在获取值前, 需要调用计算方法;
第二点是计算的速度是滑动的位移, 可能是正或负;
第三点是在计算完成后, 需要回收速度追踪器.
public class VelocityTrackerTest extends Activity {
private TextView mInfo;
private VelocityTracker mVelocityTracker;
private int mMaxVelocity;
private int mPointerId;
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
mInfo = new TextView(this);
mInfo.setLines(4);
mInfo.setLayoutParams(new ActionBar.LayoutParams(ActionBar.LayoutParams.MATCH_PARENT, ActionBar.LayoutParams.MATCH_PARENT));
mInfo.setTextColor(Color.WHITE);
setContentView(mInfo);
//获取Fling最大值
mMaxVelocity = ViewConfiguration.get(this).getScaledMaximumFlingVelocity();
}
@Override
public boolean onTouchEvent(MotionEvent event) {
final int action = event.getAction();
acquireVelocityTracker(event); //第一步
final VelocityTracker verTracker = mVelocityTracker;
switch (action) {
case MotionEvent.ACTION_DOWN:
//求第一个触点的id, 此时可能有多个触点,但至少一个
mPointerId = event.getPointerId(0);
break;
case MotionEvent.ACTION_MOVE:
//获取追踪到的速度 【第二步】
verTracker.computeCurrentVelocity(1000, mMaxVelocity);
final float velocityX = verTracker.getXVelocity(mPointerId);
final float velocityY = verTracker.getYVelocity(mPointerId);
recodeInfo(velocityX, velocityY);
break;
case MotionEvent.ACTION_UP:
releaseVelocityTracker(); //第三步
break;
case MotionEvent.ACTION_CANCEL:
releaseVelocityTracker(); //第三步
break;
default:
break;
}
return super.onTouchEvent(event);
}
/**
* @param event 向VelocityTracker添加MotionEvent
* 开始速度追踪
*/
private void acquireVelocityTracker(final MotionEvent event) {
if(null == mVelocityTracker) {
mVelocityTracker = VelocityTracker.obtain();
}
mVelocityTracker.addMovement(event);
}
/**
* 解除速度追踪
*/
private void releaseVelocityTracker() {
if(null != mVelocityTracker) {
mVelocityTracker.clear();
mVelocityTracker.recycle();
mVelocityTracker = null;
}
}
private static final String sFormatStr = "velocityX=%f\nvelocityY=%f";
/**
* 记录当前速度
*
* @param velocityX x轴速度
* @param velocityY y轴速度
*/
private void recodeInfo(final float velocityX, final float velocityY) {
final String info = String.format(sFormatStr, velocityX, velocityY);
mInfo.setText(info);
}
}
Scroller
官方解释
这个类封装了滚动。您可以使用滚动器(Scroller或OverScroller)来收集生成滚动动画所需的数据,例如,响应fling手势。滚动条跟踪您的滚动偏移量,但它们不会自动应用这些位置到您的视图。 这是你的责任,获得和应用新的坐标,将使滚动动画看起来流畅的速度。
公共方法 | |
---|---|
void | abortAnimation() 停止动画。 |
boolean | computeScrollOffset() 当你想知道新的位置调用这个 |
void | extendDuration(int extend) 扩展滚动动画。 |
void | fling(int startX, int startY, int velocityX, int velocityY, int minX, int maxX, int minY, int maxY) 基于fling手势开始滚动。 |
final void | forceFinished(boolean finished) 将完成的字段强制为特定值。 |
float | getCurrVelocity() 返回当前速度。 |
final int | getCurrX() 返回滚动中的当前X偏移量。 |
final int | getCurrY() 返回滚动中的当前Y偏移量。 |
final int | getDuration() 返回滚动事件将花费的时间(以毫秒为单位)。 |
final int | getFinalX() 返回滚动将结束X的位置。 |
final int | getFinalY() 返回滚动将结束Y的位置。 |
final int | getStartX() 返回滚动中的开始X偏移量。 |
final int | getStartY() 返回滚动中的起始Y偏移量。 |
final boolean | isFinished() 返回滚动器是否已完成滚动。 |
void | setFinalX(int newX) 设置此滚动条的最终位置(X)。 |
void | setFinalY(int newY) 设置此滚动条的最终位置(Y)。 |
final void | setFriction(float friction) 施加到甩毛的摩擦量。 |
void | startScroll(int startX, int startY, int dx, int dy, int duration) 通过提供起点,行进距离和滚动持续时间开始滚动。 |
void | startScroll(int startX, int startY, int dx, int dy) 通过提供起点和行进距离开始滚动。 |
int | timePassed() 返回自滚动开始所经过的时间。 |
scrollBy( )的源码
public void scrollBy(int x, int y) {
scrollTo(mScrollX + x, mScrollY + y);
}
这就是说scrollBy( )调用了scrollTo( ),scrollBy里面参数指的是偏移量,最终起作用的是scrollTo( )方法。
scroll的本质
scrollTo( )和scrollBy( )移动的只是View的内容,而且View的背景是不移动的。
scrollTo( )和scrollBy( )方法的坐标说明
scroll向右滑动为负数,向左滑动为正数;向下为负数,向上为正数。
综合运用
public class SlideCutListView extends ListView {
public interface RemoveListener {
void removeItem(RemoveDirection direction, int position);
}
//移除item后的回调接口
private RemoveListener mRemoveListener;
private int screenWidth; //屏幕宽度
private Scroller mScroller; //滚动器
private static final int SNAP_VELOCITY = 600;
private VelocityTracker velocityTracker; //速度追踪器
private int mTouchSlop ; //获取touchSlope.该值表示系统所能识别出的被认为是滑动的最小距离
private int downX; //手指按下X轴值
private int downY; //手指按下Y轴值
private int slidePostion; //当前滑动的ListView对应下标值(position)
private View itemView; //获取焦点View
private boolean isSlide = false; //是否响应滑动,默认为不响应
/**
* 用来指示item滑出屏幕的方向,向左或者向右,用一个枚举值来标记
*/
private RemoveDirection removeDirection;
// 滑动删除方向的枚举值
public enum RemoveDirection {
RIGHT, LEFT
}
public SlideCutListView(Context context, AttributeSet attrs) {
super(context, attrs);
screenWidth = ((WindowManager) context.getSystemService(Context.WINDOW_SERVICE)).getDefaultDisplay().getWidth();
mScroller = new Scroller(context);
ViewConfiguration viewConfiguration = ViewConfiguration.get(context);
mTouchSlop = viewConfiguration.getScaledTouchSlop();
}
/**
* 分发事件,主要做的是判断点击的是哪个item, 以及通过postDelayed来设置响应左右滑动事件
*/
@Override
public boolean dispatchTouchEvent(MotionEvent ev) {
switch (ev.getAction()){
case MotionEvent.ACTION_DOWN:
startVelocityTracker(ev); //开始速度追踪器
if (!mScroller.isFinished()){
return super.dispatchTouchEvent(ev);
}
downX = (int) ev.getX();
downY = (int) ev.getY();
downXx = downX;
slidePostion = pointToPosition(downX, downY);
// 无效的下标position, 不做任何处理
if (slidePostion == AdapterView.INVALID_POSITION){
return super.dispatchTouchEvent(ev);
}
itemView = getChildAt(slidePostion - getFirstVisiblePosition());
break;
case MotionEvent.ACTION_MOVE:
if (Math.abs(getScrollVelocity()) > SNAP_VELOCITY
|| (Math.abs(ev.getX() - downX)) > mTouchSlop &&
(Math.abs(ev.getY() - downY)) > mTouchSlop &&
(Math.abs(ev.getY() - downY)) < itemView.getHeight()){
isSlide = true;
}
break;
case MotionEvent.ACTION_UP:
recycleVelocityTracker();
break;
}
return super.dispatchTouchEvent(ev);
}
private int downXx;
@Override
public boolean onTouchEvent(MotionEvent ev) {
//响应了滑动 和 listView下标值是有效的
if (isSlide && slidePostion != AdapterView.INVALID_POSITION){
//父类事件不拦截该控件上的触摸事件
requestDisallowInterceptTouchEvent(true);
startVelocityTracker(ev);//开始速度追踪器
int x = (int) ev.getX();
final int action = ev.getAction();
switch (action){
case MotionEvent.ACTION_DOWN:
break;
case MotionEvent.ACTION_MOVE:
//创建一个新的MotionEvent,从现有的复制
MotionEvent newEvent = MotionEvent.obtain(ev);
newEvent.setAction(MotionEvent.ACTION_CANCEL |
(ev.getActionIndex()<< MotionEvent.ACTION_POINTER_INDEX_SHIFT));
onTouchEvent(newEvent);
int deltaX = downXx - x;
downXx = x;
if ((Math.abs(ev.getX() - downX)) > mTouchSlop &&
(Math.abs(ev.getY() - downY)) > mTouchSlop &&
(Math.abs(ev.getY() - downY)) < itemView.getHeight()){
itemView.scrollBy(deltaX,0);
}else{
// 手指拖动itemView滚动, deltaX大于0向左滚动,小于0向右滚
itemView.scrollTo(0, 0);
}
//自个打这次事件消费掉了,不继续向下分发了
return true;
case MotionEvent.ACTION_UP:
int velocityX = getScrollVelocity();
if (velocityX > SNAP_VELOCITY){
scrollRight();
}else if (velocityX < -SNAP_VELOCITY){
scrollLeft();
}else{
scrollByDistanceX();
}
recycleVelocityTracker();
// 手指离开的时候就不响应左右滚动
isSlide = false;
break;
}
}
return super.onTouchEvent(ev);
}
@Override
public void computeScroll() {
// 调用startScroll的时候mScroller.computeScrollOffset()返回true
if (mScroller.computeScrollOffset()){
// 让ListView item根据当前的滚动偏移量进行滚动
itemView.scrollTo(mScroller.getCurrX(), mScroller.getCurrY());
postInvalidate();
// 滚动动画结束的时候调用回调接口
if (mScroller.isFinished()) {
if (mRemoveListener == null) {
throw new NullPointerException("RemoveListener is null, we should called setRemoveListener()");
}
itemView.scrollTo(0, 0);
mRemoveListener.removeItem(removeDirection, slidePostion);
}
}
}
/**
* 向左滑动,根据上面我们知道向左滑动为正值
*/
private void scrollLeft() {
removeDirection = RemoveDirection.LEFT;
final int delta = (screenWidth - itemView.getScrollX());
// 调用startScroll方法来设置一些滚动的参数,我们在computeScroll()方法中调用scrollTo来滚动item
mScroller.startScroll(itemView.getScrollX(), 0, delta, 0,
Math.abs(delta));
postInvalidate(); // 刷新itemView
}
/**
* 根据手指滚动itemView的距离来判断是滚动到开始位置还是向左或者向右滚动
*/
private void scrollByDistanceX() {
// 如果向左滚动的距离大于屏幕的二分之一,就让其删除
if (itemView.getScrollX() >= screenWidth / 2) {
scrollLeft();
} else if (itemView.getScrollX() <= -screenWidth / 2) {
scrollRight();
} else {
// 滚回到原始位置,为了偷下懒这里是直接调用scrollTo滚动
itemView.scrollTo(0, 0);
}
}
/**
* 往右滑动,getScrollX()返回的是左边缘的距离,就是以View左边缘为原点
* 到开始滑动的距离,所以向右边滑动为负值
*/
private void scrollRight() {
removeDirection = RemoveDirection.RIGHT;
int delta = screenWidth + itemView.getScrollX();
// 调用startScroll方法来设置一些滚动的参数,
// 我们在computeScroll()方法中调用scrollTo来滚动item
mScroller.startScroll(itemView.getScrollX(), 0, -delta, 0,Math.abs(delta));
postInvalidate(); // 刷新itemView
}
private void startVelocityTracker(MotionEvent ev) {
if (velocityTracker == null) {
velocityTracker = VelocityTracker.obtain();
}
velocityTracker.addMovement(ev);
}
//获取追踪到的X轴的速度
private int getScrollVelocity() {
velocityTracker.computeCurrentVelocity(1000);//1s
int velocity = (int) velocityTracker.getXVelocity();
return velocity;
}
/**
* 移除用户速度跟踪器
*/
private void recycleVelocityTracker() {
if (velocityTracker != null) {
velocityTracker.recycle();
velocityTracker = null;
}
}
//设置滑动删除的回调接口
public void setRemoveListener(RemoveListener removeListener) {
this.mRemoveListener = removeListener;
}
}
activity的xml
<?xml version="1.0" encoding="utf-8"?>
<RelativeLayout xmlns:android="http://schemas.android.com/apk/res/android"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:background="@android:color/darker_gray">
<com.hh.person.customview.widget.SlideCutListView
android:id="@+id/slideCutListView"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:listSelector="@android:color/transparent"
android:cacheColorHint="@android:color/transparent">
</com.hh.person.customview.widget.SlideCutListView>
</RelativeLayout>
adapter的xml
<?xml version="1.0" encoding="UTF-8"?>
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
android:layout_width="match_parent"
android:layout_height="wrap_content" >
<TextView
android:id="@+id/list_item"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_margin="15dip" />
</LinearLayout>
Activity
public class ScrollerActivity extends Activity implements SlideCutListView.RemoveListener{
private SlideCutListView slideCutListView ;
private ArrayAdapter<String> adapter;
private List<String> dataSourceList = new ArrayList<>();
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_scroller);
slideCutListView = (SlideCutListView) findViewById(R.id.slideCutListView);
init();
}
private void init() {
slideCutListView.setRemoveListener(this);
for(int i=0; i<20; i++){
dataSourceList.add("滑动删除" + i);
}
adapter = new ArrayAdapter<>(this, R.layout.listview_item, R.id.list_item, dataSourceList);
slideCutListView.setAdapter(adapter);
slideCutListView.setOnItemClickListener(new AdapterView.OnItemClickListener() {
@Override
public void onItemClick(AdapterView<?> parent, View view,
int position, long id) {
}
});
}
//滑动删除之后的回调方法
@Override
public void removeItem(SlideCutListView.RemoveDirection direction, int position) {
adapter.remove(adapter.getItem(position));
switch (direction) {
case RIGHT:
Toast.makeText(this, "向右删除 "+ position, Toast.LENGTH_SHORT).show();
break;
case LEFT:
Toast.makeText(this, "向左删除 "+ position, Toast.LENGTH_SHORT).show();
break;
default:
break;
}
}
}
ViewDragHelper
官方解释
ViewDragHelper是一个用于编写自定义ViewGroups的实用程序类。 它提供了许多有用的操作和状态跟踪,允许用户在其父ViewGroup中拖动和重新定位视图。
常量 | |
---|---|
int | DIRECTION_ALL 表示沿所有轴进行检查 |
int | DIRECTION_HORIZONTAL 表示检查应沿水平轴进行 |
int | DIRECTION_VERTICAL 表示检查应沿垂直轴进行 |
int | EDGE_ALL 边缘标志集指示所有边缘都应受影响。 |
int | EDGE_BOTTOM 指示底部边缘应受影响的边缘标志。 |
int | EDGE_LEFT 指示左边缘应受影响的边缘标志。 |
int | EDGE_RIGHT 边缘标志,指示右边缘应受影响。 |
int | EDGE_TOP 边缘标志,指示上边缘应受影响。 |
int | INVALID_POINTER 空/无效指针ID。 |
int | STATE_DRAGGING 目前正在拖曳视图。 |
int | STATE_IDLE 由于fling / snap,当前没有拖动或动画视图。 |
int | STATE_SETTLING 视图作为飞行或预定义的非交互式运动的结果目前正在固定到位。 |
Public methods | |
---|---|
void | abort()
|
void | cancel() 调用此方法的结果相当于process Touch Event(android.view.MotionEvent)接收ACTION_CANCEL事件。 |
void | captureChildView(View childView, int activePointerId) 捕获特定的子视图以在父对象中拖动。 |
boolean | checkTouchSlop(int directions, int pointerId) 检查在当前手势中跟踪的指定指针是否已经超过所需的斜率阈值。 |
boolean | checkTouchSlop(int directions) 检查在当前手势中跟踪的任何指针是否已经越过所需的斜率阈值。 |
boolean | continueSettling(boolean deferCallbacks) 将捕获的建立视图移动当前时间的适当量。 |
staticViewDragHelper | create(ViewGroup forParent, float sensitivity, ViewDragHelper.Callback cb) Factory方法创建一个新的ViewDragHelper。 |
staticViewDragHelper | create(ViewGroup forParent, ViewDragHelper.Callback cb) Factory方法创建一个新的ViewDragHelper。 |
View | findTopChildUnder(int x, int y) 在父视图的坐标系内找到给定点下的最顶层子项。 |
void | flingCapturedView(int minLeft, int minTop, int maxLeft, int maxTop) 基于标准自由移动甩水行为设置捕获的视图。 |
int | getActivePointerId() |
View | getCapturedView() |
int | getEdgeSize() 返回边的大小。 |
float | getMinVelocity() 返回当前配置的最小速度。 |
int | getTouchSlop() |
int | getViewDragState() 检索此帮助器的当前拖动状态。 |
boolean | isCapturedViewUnder(int x, int y) 确定当前捕获的视图是否在父视图的坐标系中的给定点下。 |
boolean | isEdgeTouched(int edges) 检查指定的任何边是否最初在当前活动的手势中被触摸。 |
boolean | isEdgeTouched(int edges, int pointerId) 检查指定的边是否最初被指定的ID触摸了指针。 |
boolean | isPointerDown(int pointerId) 检查给定的指针ID是否表示当前关闭的指针(根据ViewDragHelper的知识)。 |
boolean | isViewUnder(View view, int x, int y) 确定提供的视图是否在父视图的坐标系中的给定点下。 |
void | processTouchEvent(MotionEvent ev) 处理父视图接收的触摸事件。 |
void | setEdgeTrackingEnabled(int edgeFlags) 对父视图的所选边缘启用边缘跟踪。 |
void | setMinVelocity(float minVel) 设置将被检测为具有大于零的幅度(以像素每秒为单位)的最小速度。 |
boolean | settleCapturedViewAt(int finalLeft, int finalTop) 在给定(左,顶)位置处设置捕获的视图。 |
boolean | shouldInterceptTouchEvent(MotionEvent ev) 检查提供给父视图的onInterceptTouchEvent的此事件是否应导致父级拦截触摸事件流。 |
boolean | smoothSlideViewTo(View child, int finalLeft, int finalTop) 将视图子视图动画化为给定(左,顶)位置。 |
ViewDragHelper.Callback是连接ViewDragHelper与view之间的桥梁(这个view一般是指拥子view的容器即parentView)
ViewDragHelper可以检测到是否触及到边缘
ViewDragHelper并不是直接作用于要被拖动的View,而是使其控制的视图容器中的子View可以被拖动,如果要指定某个子view的行为,需要在Callback中想办法;
ViewDragHelper的本质其实是分析onInterceptTouchEvent和onTouchEvent的MotionEvent参数,然后根据分析的结果去改变一个容器中被拖动子View的位置( 通过 offsetTopAndBottom(int offset)和offsetLeftAndRight(int offset)方法 ),他能在触摸的时候判断当前拖动的是哪个子View;
虽然ViewDragHelper的实例方法 ViewDragHelper.create(ViewGroup forParent, Callback cb) 可以指定一个被ViewDragHelper处理拖动事件的对象 ,但ViewDragHelper类的设计决定了其适用于被包含在一个自定义ViewGroup之中,而不是对任意一个布局上的视图容器使用ViewDragHelper。
想对ViewDragHelper更多了解,可以 参考这边博文
基本用法
public class VDHLayout extends LinearLayout{
private ViewDragHelper viewDragHelper;
private View mDragView;
private View mAutoBackView;
private View mEdgeTrackerView;
private Point mAutoBackOriginPos = new Point();
public VDHLayout(Context context, AttributeSet attrs) {
super(context, attrs);
//第二个参数就是滑动灵敏度【1.0f是正常的,较大的值更敏感。】
viewDragHelper = ViewDragHelper.create(this, 1.0f, new ViewDragHelper.Callback() {
//这个地方实际上函数的返回值为true则代表可以滑动,为false则不能滑动
@Override
public boolean tryCaptureView(View child, int pointerId) {
//mEdgeTrackerView禁止直接移动
return child == mDragView || child == mAutoBackView;
}
/**
* 处理水平滑动。如果要实现水平方向滑动效果,该方法必须重写。因为它的默认值为0,即不发生滑动。
* 通常情况下,只需返回left即可,当但需要更加精确地计算padding等属性的时候,就需要对left进行处理,
* 并返回合适大小的值。
* @param child
* @param left 水平方向上child移动的距离
* @param dx 比较前一次的增量
* @return
*/
@Override
public int clampViewPositionHorizontal(View child, int left, int dx) {
return left;
}
/**
* 处理垂直滑动。如果要实现垂直方向滑动效果,该方法必须重写。因为它的默认值为0,即不发生滑动。
* @param child
* @param top 垂直方向上child移动的距离
* @param dy 比较前一次的增量
* @return
*/
@Override
public int clampViewPositionVertical(View child, int top, int dy) {
return top;
}
/**
* 该方法拖动结束后调用,通过重写该方法,可以非常简单地实现当手指离开屏幕后要实现的操作,这个方法内部是
* 通过Scroller类来实现的,这也是重写computeScroll()方法的原因
* @param releasedChild
* @param xvel
* @param yvel
*/
@Override
public void onViewReleased(View releasedChild, float xvel, float yvel) {
//mAutoBackView手指释放是可以自动回去
if (releasedChild == mAutoBackView){
viewDragHelper.settleCapturedViewAt(mAutoBackOriginPos.x,mAutoBackOriginPos.y);
invalidate();
}
}
//在边界拖动是回调
@Override
public void onEdgeDragStarted(int edgeFlags, int pointerId) {
viewDragHelper.captureChildView(mEdgeTrackerView,pointerId);
}
});
viewDragHelper.setEdgeTrackingEnabled(ViewDragHelper.EDGE_LEFT);
}
@Override
public boolean onInterceptTouchEvent(MotionEvent ev) {
//将触摸事件传递给ViewDragHelper,此操作必不可少
return viewDragHelper.shouldInterceptTouchEvent(ev);
}
@Override
public boolean onTouchEvent(MotionEvent event) {
viewDragHelper.processTouchEvent(event);
return true;
}
@Override
public void computeScroll() {
if (viewDragHelper.continueSettling(true)){
invalidate();
}
}
@Override
protected void onLayout(boolean changed, int l, int t, int r, int b) {
super.onLayout(changed, l, t, r, b);
mAutoBackOriginPos.x = mAutoBackView.getLeft();
mAutoBackOriginPos.y = mAutoBackView.getTop();
}
//在所用的子View完成后调用该方法
@Override
protected void onFinishInflate() {
super.onFinishInflate();
mDragView = getChildAt(0);
mAutoBackView = getChildAt(1);
mEdgeTrackerView = getChildAt(2);
}
}
xml
<RelativeLayout xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:tools="http://schemas.android.com/tools"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:paddingLeft="@dimen/activity_horizontal_margin"
android:paddingRight="@dimen/activity_horizontal_margin"
android:paddingTop="@dimen/activity_vertical_margin"
android:paddingBottom="@dimen/activity_vertical_margin"
tools:context=".MainActivity">
<com.hh.person.customview.widget.VDHLayout
android:layout_width="match_parent"
android:layout_height="match_parent"
android:orientation="vertical">
<TextView
android:layout_width="50dp"
android:layout_height="50dp"
android:background="#f00"/>
<TextView
android:layout_marginTop="10dp"
android:layout_width="50dp"
android:layout_height="50dp"
android:background="#0f0"/>
<TextView
android:layout_marginTop="10dp"
android:layout_width="50dp"
android:layout_height="50dp"
android:background="#00f"/>
</com.hh.person.customview.widget.VDHLayout>
</RelativeLayout>