随便百度搜了几篇看看
https://www.jianshu.com/p/111a7bc76a0e
https://blog.csdn.net/itermeng/article/details/52159637?hmsr=toutiao.io&utm_medium=toutiao.io&utm_source=toutiao.io
https://blog.csdn.net/briblue/article/details/73730386
https://blog.csdn.net/coder_nice/article/details/44958341
文章最后有修改后的侧滑删除代码
代码如下:
import android.content.Context
import android.support.v4.widget.ViewDragHelper
import android.util.AttributeSet
import android.widget.LinearLayout
import android.view.MotionEvent
import android.view.View
import com.charliesong.demo0327.R
import kotlinx.android.synthetic.main.activity_words.*
/**
* Created by charlie.song on 2018/4/28.
*/
class LinearLayoutDrag:LinearLayout{
constructor(context: Context?) : super(context){
initDrag()
}
constructor(context: Context?, attrs: AttributeSet?) : super(context, attrs){
initDrag()
}
constructor(context: Context?, attrs: AttributeSet?, defStyleAttr: Int) : super(context, attrs, defStyleAttr){
initDrag()
}
private lateinit var viewDragHelper: ViewDragHelper
private var oldLeft=0;
private var oldTop=0
private fun initDrag(){
viewDragHelper= ViewDragHelper.create(this,object : ViewDragHelper.Callback(){
override fun tryCaptureView(child: View, pointerId: Int): Boolean {
println("try capture ${child} pointer id=$pointerId")
if(child.id== R.id.tv_word_insert){
oldLeft=child.left
oldTop=child.top
return true
}
return false
}
//返回值用来限制控件可以移动的范围的
override fun clampViewPositionHorizontal(child: View?, left: Int, dx: Int): Int {
return left
}
override fun clampViewPositionVertical(child: View?, top: Int, dy: Int): Int {
return top
}
override fun onViewPositionChanged(changedView: View, left: Int, top: Int, dx: Int, dy: Int) {
super.onViewPositionChanged(changedView, left, top, dx, dy)
println("change ==$left $top $dx $dy")
}
override fun onViewReleased(releasedChild: View?, xvel: Float, yvel: Float) {
super.onViewReleased(releasedChild, xvel, yvel)
println("release= $xvel $yvel $oldLeft -- $oldTop")
//这是就是反弹回初始位置
viewDragHelper.settleCapturedViewAt(oldLeft,oldTop)
postInvalidate()
}
})
}
//处理是否拦截
override fun onInterceptTouchEvent(ev: MotionEvent): Boolean {
//由viewDragHelper 来判断是否应该拦截此事件
return viewDragHelper.shouldInterceptTouchEvent(ev)
}
override fun onTouchEvent(event: MotionEvent): Boolean {
//将触摸事件传给viewDragHelper来解析处理
viewDragHelper.processTouchEvent(event)
//消费掉此事件,自己来处理
return true
}
override fun computeScroll() {
if (viewDragHelper.continueSettling(true)) {
invalidate()
}
}
}
需要说明下,在onViewRelease这个方法里如果要回弹这个view
用如下的方法
// viewDragHelper.settleCapturedViewAt(oldLeft,oldTop)
// postInvalidate()
还需要实现下边的方法,否则无效
override fun computeScroll() {
if (viewDragHelper.continueSettling(true)) {
invalidate()
}
}
实际操作,让一个child移动,下边的流程
首先打印下callback的日志
- 可以发现最先进入的是getOrderedChildIndex方法,这个方法里的index是由当前点击的view的index决定的。如果viewgroup里有4个view,当前点击的view的childindex是2的话,那么会返回3,和2,也就是在它之后的index会倒叙打印一遍。如果点在空白处或者首个view上,那么就会打印3,2,1,0
看下draghelper的方法
shouldInterceptTouchEvent(ev) 和processTouchEvent(event),
里边都调用了下下代码,可以看到是从上往下找child的,所以getOrderedChildIndex会打印2次
public View findTopChildUnder(int x, int y) {
final int childCount = mParentView.getChildCount();
for (int i = childCount - 1; i >= 0; i--) {
final View child = mParentView.getChildAt(mCallback.getOrderedChildIndex(i));
if (x >= child.getLeft() && x < child.getRight()
&& y >= child.getTop() && y < child.getBottom()) {
return child;
}
}
return null;
}
- 根据1的方法,如果你点击的位置有一个view,那么会走tryCaptureView方法,如果没有那就结束了,
根据tryCaptureView返回true还是false,才会继续走下边的方法 - 如果上边你返回了true,会走onViewCaptured
- onViewDragStateChanged 这时候这个view的状态就成了dragging了
如果进行了移动,也就是有了action_move,那么会走5的方法,否则直接就到7了
clampViewPositionHorizontal(child: View, left: Int, dx: Int)
dx :水平方向移动的距离,left:移动后的left坐标,也就是child原来的left加上前边的dx
需要返回一个值,也就是这个child的left的新的值,这里可以做一些限制,
比如不让滚出左侧屏幕,就可以判断left如果小于0,直接返回0即可
clampViewPositionVertical(child: View, top: Int, dy: Int)
垂直方向的一个道理onViewPositionChanged 方法5返回值以后,如果left或者top发生了变化,就会走这里了
onViewReleased 手指松开,释放状态,这时候可以添加条件,然后决定要做啥,比如可以让这个view回到原来的位置,或者滚动到别的地方,可以使用如下的方法
//这个方法也可以 dragHelper.smoothSlideViewTo()
dragHelper.settleCapturedViewAt(0,releasedChild.top)
viewGroup.postInvalidate()
- onViewDragStateChanged 滚动结束以后,状态又成0了
流程日志如下
tryCaptureView======android.support.design.widget.AppBarLayout{574a200 V.E...... ........ 0,0-934,220 #7f080021 app:id/app_bar}======0
onViewCaptured================android.support.design.widget.AppBarLayout{574a200 V.E...... ........ 0,0-934,220 #7f080021 app:id/app_bar}======0
onViewDragStateChanged=========1
clampViewPositionVertical==============top/dy=1=1
onViewPositionChanged===============0/2/==dx/dy===0/2
clampViewPositionVertical==============top/dy=3=1
onViewPositionChanged===============0/4/==dx/dy===0/2
onViewReleased============0.0==0.0
onViewDragStateChanged=========0
其他2个方法
刚开始以为这玩意没啥用,后来搜了下https://www.jianshu.com/p/5670a67f0b19
发现这2个方法是对那些本身有触摸事件的view才起作用,比如button,checkbox等
不过这个返回值好像大于0,上边的button,checkbox就可以移动,小于等于0的不能移动,
对应2个方向,看需求决定返回值
override fun getViewHorizontalDragRange(child: View): Int
override fun getViewVerticalDragRange(child: View): Int
具体可参考源码dragHelper.shouldInterceptTouchEvent(ev)
case MotionEvent.ACTION_MOVE: {
//checkTouchSlop方法里边调用了getViewHorizontalDragRange的方法来返回结果,如果2个方向都为0的话,返回的是false
final boolean pastSlop = toCapture != null && checkTouchSlop(toCapture, dx, dy);
if (pastSlop) {
final int hDragRange = mCallback.getViewHorizontalDragRange(toCapture);
final int vDragRange = mCallback.getViewVerticalDragRange(toCapture);
if ((hDragRange == 0 || (hDragRange > 0 && newLeft == oldLeft))
&& (vDragRange == 0 || (vDragRange > 0 && newTop == oldTop))) {
break;
}
if (pastSlop && tryCaptureViewForDrag(toCapture, pointerId)) {
break;
}
这里是上边代码里使用的checkTouchSlop()
private boolean checkTouchSlop(View child, float dx, float dy) {
if (child == null) {
return false;
}
final boolean checkHorizontal = mCallback.getViewHorizontalDragRange(child) > 0;
final boolean checkVertical = mCallback.getViewVerticalDragRange(child) > 0;
if (checkHorizontal && checkVertical) {
return dx * dx + dy * dy > mTouchSlop * mTouchSlop;
} else if (checkHorizontal) {
return Math.abs(dx) > mTouchSlop;
} else if (checkVertical) {
return Math.abs(dy) > mTouchSlop;
}
return false;
}
edge边界捕捉处理
- 监听哪个方向,上下左右,可以选
如下,想要处理哪个方向,就在后边加上哪个,EDGE_ALL是4个方法都监听
draghelper?.setEdgeTrackingEnabled(ViewDragHelper.EDGE_LEFT or ViewDragHelper.EDGE_RIGHT)
- onEdgeTouched(edgeFlags: Int, pointerId: Int)
我们在步骤1里设置了监听的flag以后,如果手指触摸了相应的flag,就会走这里,在这里就可以处理。
如下,我们可以监听到左侧边界事件以后,把触摸事件交给一个child来处理。
captureChildView 就是把某个child设置为当前正在capture的状态,之后就和前边捕获child一样了
override fun onEdgeTouched(edgeFlags: Int, pointerId: Int) {
super.onEdgeTouched(edgeFlags, pointerId)// 4 2 8
if(edgeFlags==ViewDragHelper.EDGE_LEFT&&viewGroup.childCount>0){
dragHelper.captureChildView(viewGroup.getChildAt(0),pointerId)
}
}
下边简单分析下各个方法的作用
如果要测试边界触摸功能,需要手动开启,4个方向自己选,或者选个all就都有了。
draghelper?.setEdgeTrackingEnabled(ViewDragHelper.EDGE_LEFT or ViewDragHelper.EDGE_RIGHT)
inner class CallbackBottom2 : ViewDragHelper.Callback {
var viewGroup: ViewGroup
var childTop: View
constructor(viewGroup: ViewGroup) : super() {
this.viewGroup = viewGroup
childTop = viewGroup.getChildAt(0)
}
var point = Point()
override fun tryCaptureView(child: View, pointerId: Int): Boolean {
//返回true表示这个child允许捕获,才会有后边的操作,返回false也就没有后边的操作了,这里可以根据child来决定哪个需要移动
println("tryCaptureView======$child======$pointerId")
return true
}
//tryCaptureView返回true就会走这里,或者在edge的时候draghelper?.captureChildView里传一个child也会走这里
override fun onViewCaptured(capturedChild: View, activePointerId: Int) {
super.onViewCaptured(capturedChild, activePointerId)
point.x = capturedChild.left
point.y = capturedChild.top
println("onViewCaptured================$capturedChild======$activePointerId")
}
//这个就是你如果上边返回true,那么就成了dragging状态,
// 手指离开屏幕onViewReleased如果啥也不操作就成了idle状态了。
//如果这时候我们settleCapturedViewAt让它回到原始位置,肯定需要时间的,这个时候的状态就是setting了。
override fun onViewDragStateChanged(state: Int) {
super.onViewDragStateChanged(state)
println("onViewDragStateChanged=========$state") //STATE_IDLE STATE_DRAGGING STATE_SETTLING
}
//手指离开屏幕会走这里,当然前提是有captured的view
override fun onViewReleased(releasedChild: View, xvel: Float, yvel: Float) {
super.onViewReleased(releasedChild, xvel, yvel)
println("onViewReleased============$xvel==$yvel")
draghelper?.settleCapturedViewAt(point.x, point.y)
postInvalidate()
}
//需要draghelper设置支持的边界才能生效draghelper.setEdgeTrackingEnabled(ViewDragHelper.EDGE_LEFT or ViewDragHelper.EDGE_RIGHT)
override fun onEdgeTouched(edgeFlags: Int, pointerId: Int) {
super.onEdgeTouched(edgeFlags, pointerId)//1 4 2 8
println("onEdgeTouched====================$edgeFlags========$pointerId")
if(edgeFlags==ViewDragHelper.EDGE_RIGHT){
//边界触摸的时候要操作那个child就把它传进去即可
draghelper?.captureChildView(findViewById(R.id.tv_right),pointerId)
}
}
override fun onEdgeLock(edgeFlags: Int): Boolean {
println("onEdgeLock================$edgeFlags")
return super.onEdgeLock(edgeFlags)
}
//也不知道啥用,onEdgeTouched以后,这时候触摸的地方如果没有capturedview的就会走这里
override fun onEdgeDragStarted(edgeFlags: Int, pointerId: Int) {
super.onEdgeDragStarted(edgeFlags, pointerId)
println("onEdgeDragStarted===============$edgeFlags=======$pointerId")
}
override fun getOrderedChildIndex(index: Int): Int {
println("getOrderedChildIndex===============$index")
return super.getOrderedChildIndex(index)
}
//返回0的话就不能垂直移动
override fun getViewVerticalDragRange(child: View): Int {
println("getViewVerticalDragRange=========$child=======${child.top}")
return 110
}
override fun getViewHorizontalDragRange(child: View): Int {
println("getViewHorizontalDragRange=========${child.left}")
return 110
}
//capture view以后继续移动手指,就会走这里,返回0表示不移动,返回其他值表示view新的left位置
override fun clampViewPositionHorizontal(child: View, left: Int, dx: Int): Int {
println("clampViewPositionHorizontal============left/dx====$left/$dx")
// return super.clampViewPositionHorizontal(child, left, dx)
return if (left + dx <= 0) 0 else left + dx
}
//capture view以后继续移动手指,就会走这里,返回0表示不移动,返回其他值表示view新的top位置
override fun clampViewPositionVertical(child: View, top: Int, dy: Int): Int {
println("clampViewPositionVertical==============top/dy=$top=$dy")
return if (top + dy < 0) 0 else top + dy
}
//执行了上边clamp的方法就会走这里
override fun onViewPositionChanged(changedView: View, left: Int, top: Int, dx: Int, dy: Int) {
super.onViewPositionChanged(changedView, left, top, dx, dy)
println("onViewPositionChanged===============$left/$top/==dx/dy===$dx/$dy")
}
}
写个简单的侧滑关闭页面的
监听下edge_left事件,完事release的时候判断下,速度大于500或者当前位置大于宽度一半,就滚动到右边,完事在state事件里处理,finish掉页面。
class LeftEdgeTouchCloseLayout : FrameLayout {
constructor(context: Context?) : super(context)
constructor(context: Context?, attrs: AttributeSet?) : super(context, attrs)
constructor(context: Context?, attrs: AttributeSet?, defStyleAttr: Int) : super(context, attrs, defStyleAttr)
var draghelper: ViewDragHelper? = null
private fun makesureHelper() {
if (draghelper == null) {
draghelper = ViewDragHelper.create(this, 1f, CallbackBottom2(this))
}
draghelper?.setEdgeTrackingEnabled(ViewDragHelper.EDGE_LEFT)
}
override fun onInterceptTouchEvent(ev: MotionEvent): Boolean {
makesureHelper()
return draghelper!!.shouldInterceptTouchEvent(ev)
}
override fun onTouchEvent(event: MotionEvent): Boolean {
makesureHelper()
draghelper!!.processTouchEvent(event)
return true
}
override fun computeScroll() {
super.computeScroll()
draghelper?.apply {
if (this.continueSettling(true)) {
postInvalidate()
}
}
}
inner class CallbackBottom2 : ViewDragHelper.Callback {
var viewGroup: ViewGroup
constructor(viewGroup: ViewGroup) : super() {
this.viewGroup = viewGroup
}
//这个就是你如果上边返回true,那么就成了dragging状态,
// 手指离开屏幕onViewReleased如果啥也不操作就成了idle状态了。
//如果这时候我们settleCapturedViewAt让它回到原始位置,肯定需要时间的,这个时候的状态就是setting了。
override fun onViewDragStateChanged(state: Int) {
super.onViewDragStateChanged(state)
println("onViewDragStateChanged=========$state") //STATE_IDLE STATE_DRAGGING STATE_SETTLING
when(state){
ViewDragHelper.STATE_IDLE->{
if(viewGroup.getChildAt(0).left>1){
//如果不关闭页面的话,left应该是0
(viewGroup.context as Activity).finish()
}
}
}
}
//手指离开屏幕会走这里,当然前提是有captured的view
override fun onViewReleased(releasedChild: View, xvel: Float, yvel: Float) {
super.onViewReleased(releasedChild, xvel, yvel)
println("onViewReleased============$xvel==$yvel")
var actionX=0
if(xvel>500||releasedChild.left>=viewGroup.width/2){
//关闭页面
actionX=viewGroup.width
}
draghelper?.settleCapturedViewAt(actionX,0)
postInvalidate()
}
//需要draghelper设置支持的边界才能生效draghelper.setEdgeTrackingEnabled(ViewDragHelper.EDGE_LEFT)
override fun onEdgeTouched(edgeFlags: Int, pointerId: Int) {
super.onEdgeTouched(edgeFlags, pointerId)//1 4 2 8
println("onEdgeTouched====================$edgeFlags========$pointerId")
if(edgeFlags==ViewDragHelper.EDGE_LEFT){
//边界触摸的时候要操作那个child就把它传进去即可
draghelper?.captureChildView(viewGroup.getChildAt(0),pointerId)
}
}
//capture view以后继续移动手指,就会走这里,返回0表示不移动,返回其他值表示view新的left位置
override fun clampViewPositionHorizontal(child: View, left: Int, dx: Int): Int {
println("clampViewPositionHorizontal============left/dx====$left/$dx")
// return super.clampViewPositionHorizontal(child, left, dx)
return if (left + dx <= 0) 0 else left + dx
}
}
}
之后主题弄成背景透明,要不我们滑动的时候还能能看到一个白色的背景,也就没撒用了,我们要看到的是底层activity的页面。
<item name="android:windowIsTranslucent">true</item>
<item name="android:windowBackground">@android:color/transparent</item>
在之后,基类里写下,替换掉原有的布局,外边包裹一层我们上边自定义的布局即可
super.setContentView(layoutResID)
(window.decorView as FrameLayout).apply {
var originalView = this.getChildAt(0)
originalView.setBackgroundColor(Color.WHITE)
this.removeView(originalView)
var addView = com.charliesong.demo0327.draghelper.LeftEdgeTouchCloseLayout(this@BaseActivity)
addView.addView(originalView, originalView.layoutParams)
this.addView(addView,
android.widget.FrameLayout.LayoutParams.MATCH_PARENT, android.widget.FrameLayout.LayoutParams.MATCH_PARENT)
}
好了,如此,一个简单的侧滑关闭页面的功能就实现了。
开始测试,普通页面没啥问题。有布局实时发生变化的页面就不行
比如有一个页面,我写的类似弹幕那种,每隔一秒添加一个view或者删除一个view。我发现这个页面不行,滑动的时候就自动滚回去了。因为手指虽然还在屏幕上,可貌似触摸事件自动取消了,进入了onViewReleased方法了。可能那边刷新布局。
还有类似这种recyclerview不停的添加删除数据也会引起触摸事件失效的
val viewRunnble = object : Runnable {
override fun run() {
adapterRV.apply {
if (adapterRV.itemCount < 4) {
this.datas.add(messages[index % messages.size])//最后一个位置添加数据并notify
index++
notifyItemInserted(datas.size - 1)
} else {
this.datas.removeAt(0)//删除第一条数据
rv_toast.adapter.notifyItemRemoved(0)
}
}
handler.postDelayed(this, 1000)
}
}
最后的感觉就是如果你在滑动的时候,页面进行postinvalidate之类的操作,这个就不行了。
比如 view.setlayoutparams 这个也会刷新布局的,也就不行了。
老的不是办法的办法
后边重写了LeftEdgeTouchCloseLayout,没有这种问题了,也就不需要特殊处理了
下边代码看看就行,用不到了
//首先我给在基类里加的那个LeftEdgeTouchCloseLayout弄了个id
addView.id= R.id.edgetouchid
//完事上边的runnable里判断下是否我们是否已经开始侧滑了【侧滑的话肯定left不是0了】
findViewById<ViewGroup>(R.id.edgetouchid).getChildAt(0).left>0
//如果侧滑的话我就不进行操作了,直接handler.postDelayed(this, 1000)
源码分析
简单分析下源码,就知道callback里回调的参数意义,以及啥时候调用了
draghelper!!.processTouchEvent(event)
case MotionEvent.ACTION_MOVE: {
if (mDragState == STATE_DRAGGING) {
// If pointer is invalid then skip the ACTION_MOVE.
if (!isValidPointerForActionMove(mActivePointerId)) break;
final int index = ev.findPointerIndex(mActivePointerId);
final float x = ev.getX(index);
final float y = ev.getY(index);
final int idx = (int) (x - mLastMotionX[mActivePointerId]);
final int idy = (int) (y - mLastMotionY[mActivePointerId]);
dragTo(mCapturedView.getLeft() + idx, mCapturedView.getTop() + idy, idx, idy);
saveLastMotion(ev);
} else {
// Check to see if any pointer is now over a draggable view.
final int pointerCount = ev.getPointerCount();
for (int i = 0; i < pointerCount; i++) {
final int pointerId = ev.getPointerId(i);
// If pointer is invalid then skip the ACTION_MOVE.
if (!isValidPointerForActionMove(pointerId)) continue;
final float x = ev.getX(i);
final float y = ev.getY(i);
final float dx = x - mInitialMotionX[pointerId];
final float dy = y - mInitialMotionY[pointerId];
reportNewEdgeDrags(dx, dy, pointerId);
if (mDragState == STATE_DRAGGING) {
// Callback might have started an edge drag.
break;
}
final View toCapture = findTopChildUnder((int) x, (int) y);
if (checkTouchSlop(toCapture, dx, dy)
&& tryCaptureViewForDrag(toCapture, pointerId)) {
break;
}
}
saveLastMotion(ev);
}
break;
}
dragTo :left期望值,也就是当前view的left加上手指滑动的距离,dx手指滑动的距离
private void dragTo(int left, int top, int dx, int dy) {
int clampedX = left;
int clampedY = top;
final int oldLeft = mCapturedView.getLeft();
final int oldTop = mCapturedView.getTop();
if (dx != 0) {
//这个clampedX一般返回参数里的left就行,也就是oldLeft +dx
clampedX = mCallback.clampViewPositionHorizontal(mCapturedView, left, dx);
//根据clampedX是oldLeft的差值来移动view
ViewCompat.offsetLeftAndRight(mCapturedView, clampedX - oldLeft);
}
if (dy != 0) {
clampedY = mCallback.clampViewPositionVertical(mCapturedView, top, dy);
ViewCompat.offsetTopAndBottom(mCapturedView, clampedY - oldTop);
}
if (dx != 0 || dy != 0) {
final int clampedDx = clampedX - oldLeft;
final int clampedDy = clampedY - oldTop;
//view移动后的left,top位置,以及对应的x,y轴移动的距离
mCallback.onViewPositionChanged(mCapturedView, clampedX, clampedY,
clampedDx, clampedDy);
}
}
修复处理页面会实时刷新的情况
问题来源:首先,通过ViewDragHelper,view移动的时候调用的是offsetLeftAndRight 而这个方法在页面刷新的时候被还原了。
而我们刷新页面,调用了addView,removeView等,最后都会requestLayout来刷新页面。
解决:移动view不是用offsetLeftAndRight,而使用setTranslationX
dragHelper的callback方法里,系统源码里使用offsetLeftAndRight移动dx,我们接着移动-dx还原,然后调用setTranslationX来移动view
首先主题要是透明的,因为这个侧滑要看到下层的activity的,所以主题添加如下代码
<item name="android:windowIsTranslucent">true</item>
<item name="android:windowBackground">@android:color/transparent</item>
背景透明以后,我们需要给我们的布局设置背景色,否则就看到下层了。就在我们的xml的根布局上添加个颜色,不添加也可以,工具类里默认添加了白色背景,可以修改
完整代码如下
多写了个类,方便一行代码给所有的页面添加侧滑功能
application里,第二个参数是不需要侧滑的页面,可以为null
SwipeBackUtil.init(this, arrayListOf(ActivityViewMoveTest::class.java))
//or
SwipeBackUtil.init(this)
工具SwipeBackUtil
原理很简单,里用application的registerActivityLifecycleCallbacks方法,监听activity的创建和销毁,然后修改布局
把decorView里的布局拿出来,放到我们自定义的容器里,完事把我们的容器放到decorView下。
import android.app.Activity
import android.app.Application
import android.graphics.Color
import android.os.Bundle
import android.view.View
import android.view.ViewGroup
import android.widget.FrameLayout
import com.charliesong.demo0327.R
object SwipeBackUtil {
var defaultBgColor=Color.WHITE
fun register(activity: Activity) {
if(excludes.contains(activity.javaClass)){
activity.window.decorView.setBackgroundColor(defaultBgColor)
return
}
(activity.window.decorView as FrameLayout).apply {
val originalView = this.getChildAt(0)
originalView.setBackgroundColor(defaultBgColor)
this.removeView(originalView)
val swipeBackView = LeftEdgeTouchCloseLayout(activity)
swipeBackView.id = R.id.edgetouchid
swipeBackView.addView(originalView, originalView.layoutParams)
this.addView(swipeBackView,
android.widget.FrameLayout.LayoutParams.MATCH_PARENT, android.widget.FrameLayout.LayoutParams.MATCH_PARENT)
}
}
val excludes= arrayListOf<Class<*>>()
val activitys = arrayListOf<Activity>()
fun init(app: Application,excludeClass:ArrayList<Class<*>>){
excludes.clear()
excludes.addAll(excludeClass)
init(app)
}
fun init(app: Application) {
activitys.clear()
val callback = object : Application.ActivityLifecycleCallbacks {
override fun onActivityPaused(activity: Activity?) {
}
override fun onActivityResumed(activity: Activity?) {
}
override fun onActivityStarted(activity: Activity?) {
}
override fun onActivityDestroyed(activity: Activity) {
activitys.remove(activity)
if (activitys.size == 0) {
app.unregisterActivityLifecycleCallbacks(this)
}
}
override fun onActivitySaveInstanceState(activity: Activity?, outState: Bundle?) {
}
override fun onActivityStopped(activity: Activity?) {
}
override fun onActivityCreated(activity: Activity, savedInstanceState: Bundle?) {
activitys.add(activity)
register(activity)
}
}
app.registerActivityLifecycleCallbacks(callback)
}
fun getBackView(): View? {
var preIndex = activitys.size - 2
while (preIndex >= 0) {
val activity = activitys[preIndex]
if (activity.isFinishing) {
preIndex--
} else {
return (activity.window.decorView as ViewGroup).getChildAt(0)
}
}
return null
}
fun restorBackView() {
getBackView()?.translationX=0f
}
}
自定义侧滑类LeftEdgeTouchCloseLayout
代码解析在后边
import android.app.Activity
import android.content.Context
import android.support.v4.widget.ViewDragHelper
import android.util.AttributeSet
import android.view.MotionEvent
import android.view.View
import android.view.animation.Interpolator
import android.view.inputmethod.InputMethodManager
import android.widget.FrameLayout
import android.widget.OverScroller
class LeftEdgeTouchCloseLayout : FrameLayout {
constructor(context: Context?) : super(context)
constructor(context: Context?, attrs: AttributeSet?) : super(context, attrs)
constructor(context: Context?, attrs: AttributeSet?, defStyleAttr: Int) : super(context, attrs, defStyleAttr)
var draghelper: ViewDragHelper? = null
private fun makesureHelper() {
if (draghelper == null) {
draghelper = ViewDragHelper.create(this, 1f, CallbackBottom2())
draghelper?.setEdgeTrackingEnabled(ViewDragHelper.EDGE_LEFT)
}
}
override fun onInterceptTouchEvent(ev: MotionEvent): Boolean {
makesureHelper()
return draghelper!!.shouldInterceptTouchEvent(ev)
}
override fun onTouchEvent(event: MotionEvent): Boolean {
makesureHelper()
draghelper!!.processTouchEvent(event)
return true
}
override fun computeScroll() {
super.computeScroll()
if(release){
if (scroller.computeScrollOffset()) {
val left = scroller.currX
getMoveView().translationX=left.toFloat()
postInvalidateOnAnimation()
backView?.translationX = (left - width) / factor
}else{
release=false
SwipeBackUtil.restorBackView()
if(getMoveView().translationX>10){
(context as Activity).onBackPressed()
}
}
}
}
private val sInterpolator = Interpolator { t ->
var t = t
t -= 1.0f
t * t * t * t * t + 1.0f
}
val scroller = OverScroller(context, sInterpolator)
private fun getMoveView(): View {
var index = 0
if (childCount > 1) {
index = 1
}
return getChildAt(index)
}
var backView: View? = null//底层activity的decorview
var totolMove = 0//手指总的滑动距离
var release=false//松开手指未true
inner class CallbackBottom2 : ViewDragHelper.Callback {
constructor() : super() {
}
override fun tryCaptureView(child: View, pointerId: Int): Boolean {
//返回true表示这个child允许捕获,才会有后边的操作,返回false也就没有后边的操作了,这里可以根据child来决定哪个需要移动
return false
}
//tryCaptureView返回true就会走这里,或者在edge的时候draghelper?.captureChildView里传一个child也会走这里
override fun onViewCaptured(capturedChild: View, activePointerId: Int) {
super.onViewCaptured(capturedChild, activePointerId)
totolMove = 0
release=false;
// println("onViewCaptured================$capturedChild======$activePointerId")
}
//这个就是你如果上边返回true,那么就成了dragging状态,
// 手指离开屏幕onViewReleased如果啥也不操作就成了idle状态了。
//如果这时候我们settleCapturedViewAt让它回到原始位置,肯定需要时间的,这个时候的状态就是setting了。
override fun onViewDragStateChanged(state: Int) {
super.onViewDragStateChanged(state)
println("onViewDragStateChanged=========$state") //STATE_IDLE STATE_DRAGGING STATE_SETTLING
}
//手指离开屏幕会走这里,当然前提是有captured的view
override fun onViewReleased(releasedChild: View, xvel: Float, yvel: Float) {
super.onViewReleased(releasedChild, xvel, yvel)
println("onViewReleased==========xvel/yvel==$xvel==$yvel=========left:${releasedChild.left}==${totolMove}")
var actionX = 0
if (xvel > 500 || releasedChild.translationX >= width / 2) {
//关闭页面
actionX = width
}
scroller.startScroll(totolMove,0,actionX-totolMove,0,333)
release=true
postInvalidate()
}
//需要draghelper设置支持的边界才能生效draghelper.setEdgeTrackingEnabled(ViewDragHelper.EDGE_LEFT or ViewDragHelper.EDGE_RIGHT)
override fun onEdgeTouched(edgeFlags: Int, pointerId: Int) {
super.onEdgeTouched(edgeFlags, pointerId)//1 4 2 8
// println("onEdgeTouched====================$edgeFlags========$pointerId")
if (edgeFlags == ViewDragHelper.EDGE_LEFT) {
//边界触摸的时候要操作那个child就把它传进去即可
if (backView == null) {
backView = SwipeBackUtil.getBackView()
}
draghelper?.captureChildView(getMoveView(), pointerId)
closeKeybord(this@LeftEdgeTouchCloseLayout,context)
}
}
//参数left:这个是原本的left加上dx后的值
override fun clampViewPositionHorizontal(child: View, left: Int, dx: Int): Int {
// println("clampViewPositionHorizontal============left/dx====$left/$dx===$child==${totolMove}")
totolMove += dx;
return Math.max(0, left)
}
//执行了上边clamp的方法就会走这里
override fun onViewPositionChanged(changedView: View, left: Int, top: Int, dx: Int, dy: Int) {
super.onViewPositionChanged(changedView, left, top, dx, dy)
// println("onViewPositionChanged==============$left/$top/==dx/dy===$dx/$dy")
changedView.offsetLeftAndRight(-dx)//helper里是offset dx的距离,我们这里还原回来,这个方法,布局刷新以后就还原了。
changedView.translationX = totolMove.toFloat()//修改为移动tranxlationX,这个不受布局刷新的影响
if (totolMove <= width) {
backView?.translationX = (totolMove - width) / factor
}
}
}
val factor = 3f
fun closeKeybord(v: View, mContext: Context) {
val imm = mContext.getSystemService(Context.INPUT_METHOD_SERVICE) as InputMethodManager
imm.hideSoftInputFromWindow(v.windowToken, 0)
}
}
和以前的区别,有几个地方
1.增加几个变量
var backView: View? = null//底层activity的decorview
var totolMove = 0//手指总的滑动距离
var release=false//松开手指未true
- 记录手指总的滑动距离
override fun clampViewPositionHorizontal(child: View, left: Int, dx: Int): Int {
// println("clampViewPositionHorizontal============left/dx====$left/$dx===$child==${totolMove}")
totolMove += dx;
return Math.max(0, left)
}
- 修改布局的移动方式
override fun onViewPositionChanged(changedView: View, left: Int, top: Int, dx: Int, dy: Int) {
super.onViewPositionChanged(changedView, left, top, dx, dy)
changedView.offsetLeftAndRight(-dx)//helper里是offset dx的距离,我们这里还原回来,这个方法,布局刷新以后就还原了。
changedView.translationX = totolMove.toFloat()//修改为移动tranxlationX,这个不受布局刷新的影响
if (totolMove <= width) {
backView?.translationX = (totolMove - width) / factor
}
}
4.自己声明个Scroller来处理手指松开后的滑动
val scroller = OverScroller(context, sInterpolator)
override fun onViewReleased(releasedChild: View, xvel: Float, yvel: Float) {
super.onViewReleased(releasedChild, xvel, yvel)
var actionX = 0
if (xvel > 500 || releasedChild.translationX >= width / 2) {
//关闭页面
actionX = width
}
//下边三行替换了dragerHelper的scroller事件。
scroller.startScroll(totolMove,0,actionX-totolMove,0,333)
release=true
postInvalidate()
}
5.处理scroller
override fun computeScroll() {
super.computeScroll()
if(release){
if (scroller.computeScrollOffset()) {
val left = scroller.currX
getMoveView().translationX=left.toFloat()
postInvalidateOnAnimation()
backView?.translationX = (left - width) / factor
}else{
release=false
SwipeBackUtil.restorBackView()
if(getMoveView().translationX>10){
(context as Activity).onBackPressed()
}
}
}
}
6.总结下就是
系统用的offsetLeftAndRight来移动布局,我们修改为tranxlationX。这样不受页面刷新的影响。