转载请注明出处:http://blog.csdn.net/mcy456/article/details/51067795
写着篇文章的目的很简单,在接盘项目的时候发现,前任使用的第三方滑动销毁出现了不少问题,于是心生了想要自己写一个的想法,毕竟出现问题时,能更快速的定位,更好的解决。
View的ScrollBy、ScrollTo
想要实现滑动销毁,首先要做的肯定是能够让对应的View跟着手指滑动,想要滑动到指定位置,自然也就少不了View的ScrollBy或是ScrollTo。
scrollTo 是直接位移到指定位置,而scrollBy则是基于当前的位置
scrollBy源码:
public void scrollBy(int x, int y) { scrollTo(mScrollX + x, mScrollY + y); }
由此可见,scrollBy本身是基于ScrollTo的。
有意思的是从注释:
This is called in response to an internal scroll in this view (i.e., the view scrolled its own contents)
可知 View.scrollBy、View.scrollTo 滚动的范围并不是自身在其Parent中,而是,自身的内容在自身View中的滚动,如果滚动超出了View自身大小的范围,内容会消失,但View还会在原地。
至于Scroller,在本文中就不多讲了,有意了解的直接百度......
onTouch事件的分配
博客中不少讲解Android的事件分配机制的,在这里大概描述下View的子类中大致有两种View,一种是实现了ViewParent.ViewManager接口的ViewGroup,例如个中Layout,另一种则是对View本身进行扩展的子View类似TextView、ImageView等。View中与Touch相关的方法有dispatchTouchEvent、onTouchEvent,以及一个接口OnTouchListener。继承自View的ViewGroup则多了一个方法onInterceptTouchEvent。
先讲下分配流程:
事件自最顶层View开始向下进行分配,View分配事件的方法是dispatchTouchEvent,也就是说,一个事件到达View的第一个方法必将是dispatchTouchEvent,之后,若是ViewGroup的类型,会有onInterceptTouchEvent,返回为True,则代表,该ViewGroup需要消耗此次事件不再向下传递,本次Touch的所有事件(DOWN MOVE UP)都将由本VIewGroup进行处理。如果返回为false,则会一次向下分发,由其子View或者子ViewGroup进行处理,若其子View、ViewGroup中都没有进行消耗,则会返回至本ViewGroup的onTouchEvent中(注意与OnTouchListener的onTouch进行区分),若是本ViewGroup也不消耗,则事件会交给本ViewGroup的Parent进行处理。
Activity销毁思路
明白了以上几点,完全就可以开搞做滑动销毁了。
首先,如果滑动销毁,必将上一个页面是可见的,这就需要在Application主题中设置
<item name="android:windowIsTranslucent">true</item>
其次,也需要在实现的layout中进行窗口透明设置,通过Activty中的getWindow.setBackDrawable进行设置。
准备工作完成,下边就可以开始写要实现的Layout了
import android.app.Activity; import android.content.Context; import android.graphics.Color; import android.graphics.Rect; import android.graphics.drawable.ColorDrawable; import android.graphics.drawable.Drawable; import android.util.AttributeSet; import android.view.LayoutInflater; import android.view.MotionEvent; import android.view.View; import android.view.ViewConfiguration; import android.view.ViewGroup; import android.view.ViewTreeObserver; import android.widget.FrameLayout; import android.widget.LinearLayout; import android.widget.Scroller; import com.example.angusfine.mywidget.R; /** * Created by AngusFine on 2016/3/22. */ public class SwipBackLayout extends FrameLayout implements ViewTreeObserver.OnGlobalLayoutListener{ /**l * SildingFinishLayout布局的父布局 */ private ViewGroup mParentView; /** * 滑动的最小距离 */ private int mTouchSlop; /** * 按下点的X坐标 */ private int downX; /** * 按下点的Y坐标 */ private int downY; /** * 临时存储X坐标 */ private int tempX; /** * 滑动类 */ private Scroller mScroller; /** * SildingFinishLayout的宽度 */ private int viewWidth; /** * 记录是否正在滑动 */ private boolean isSilding; private Context context; private Object o; /** * 由setContentView获取的View */ private View mChildView; /** * 判断是否结束滑动 */ private boolean isFinish; /** * 设置是否自动结束当前activity,默认自动结束 */ private boolean autoFinish = true; private float locationX = 0.1f; private boolean canScroll = false; private Rect rect; /** * 结束监听 */ OnSwipFinshed finshed; /** * 滑动中监听 */ OnSwiping swiping; Drawable drawable; ViewTreeObserver observer; private int[]location = new int[2]; public SwipBackLayout(Object o){ super((Context)o); this.o = o; this.context = (Context) o; LayoutParams params = new LayoutParams(ViewGroup.LayoutParams.MATCH_PARENT, ViewGroup.LayoutParams.MATCH_PARENT); this.setLayoutParams(params); mTouchSlop = ViewConfiguration.get(context).getScaledTouchSlop(); mScroller = new Scroller(context); rect = new Rect(); ((Activity)o).getWindow().setBackgroundDrawable(new ColorDrawable(Color.TRANSPARENT)); ViewGroup decor = (ViewGroup) ((Activity)context).getWindow().getDecorView(); for(int i = 0;i<decor.getChildCount();i++){ if(decor.getChildAt(i)instanceof LinearLayout){ ViewGroup ll = (ViewGroup)decor.getChildAt(i); for(int j = 0;j<ll.getChildCount();j++){ if (ll.getChildAt(j)instanceof FrameLayout){ this.mParentView = (ViewGroup) ll.getChildAt(j); break; } } } } observer = this.getViewTreeObserver(); observer.addOnGlobalLayoutListener(this); } public SwipBackLayout(Context context, AttributeSet attrs) { this(context, attrs, 0); } public SwipBackLayout(Context context, AttributeSet attrs, int defStyle) { super(context, attrs, defStyle); mTouchSlop = ViewConfiguration.get(context).getScaledTouchSlop(); mScroller = new Scroller(context); } @Override protected void onLayout(boolean changed, int l, int t, int r, int b) { super.onLayout(changed, l, t, r, b); if (changed) { viewWidth = this.getWidth(); mParentView.setBackground(new ColorDrawable(context.getResources().getColor(R.color.transparent_bg))); } } public View getmChildView() { return mChildView; } /** * 滚动出界面 */ private void scrollRight() { final int delta = (viewWidth + mParentView.getScrollX()); // 调用startScroll方法来设置一些滚动的参数,我们在computeScroll()方法中调用scrollTo来滚动item mScroller.startScroll(mParentView.getScrollX(), 0, -delta + 1, 0, Math.abs(delta)); postInvalidate(); } /** * 滚动到起始位置 */ private void scrollOrigin() { int delta = mParentView.getScrollX(); mScroller.startScroll(mParentView.getScrollX(), 0, -delta, 0, Math.abs(delta)); postInvalidate(); } @Override public boolean onInterceptTouchEvent(MotionEvent ev) { return true; } @Override public boolean onTouchEvent(MotionEvent event) { switch (event.getAction()) { case MotionEvent.ACTION_DOWN: downX = tempX = (int) event.getRawX(); downY = (int) event.getRawY(); if(event.getRawX()/(float)(rect.right)<locationX) { canScroll = true; } mChildView.dispatchTouchEvent(event); break; case MotionEvent.ACTION_MOVE: if(canScroll) { int moveX = (int) event.getRawX(); int deltaX = tempX - moveX; tempX = moveX; if (Math.abs(moveX - downX) > mTouchSlop && Math.abs((int) event.getRawY() - downY) < mTouchSlop) { isSilding = true; MotionEvent cancelEvent = MotionEvent.obtain(event); cancelEvent.setAction(MotionEvent.ACTION_CANCEL | (event.getActionIndex() << MotionEvent.ACTION_POINTER_INDEX_SHIFT)); mChildView.dispatchTouchEvent(cancelEvent); if (swiping != null) swiping.swiping(event, mParentView, mChildView); } else { if (!isSilding) mChildView.dispatchTouchEvent(event); } if (moveX - downX >= 0 && isSilding) { mParentView.scrollBy(deltaX, 0); drawable = mParentView.getBackground(); mChildView.getLocationOnScreen(location); drawable.setAlpha((int) ((1 - (location[0] / 1080.0f)) * 255)); return true; } }else{ mChildView.dispatchTouchEvent(event); } break; case MotionEvent.ACTION_UP: if(canScroll) { if (!isSilding) { mChildView.dispatchTouchEvent(event); } isSilding = false; if (mParentView.getScrollX() <= -viewWidth / 2) { isFinish = true; drawable.setAlpha(0); scrollRight(); } else { scrollOrigin(); isFinish = false; } }else{ mChildView.dispatchTouchEvent(event); } canScroll = false; break; } return true; } @Override public void computeScroll() { if (mScroller.computeScrollOffset()) { mParentView.scrollTo(mScroller.getCurrX(), mScroller.getCurrY()); postInvalidate(); if (mScroller.isFinished()) { if (isFinish) { if (autoFinish) { ((Activity) o).finish(); } else { if(finshed!=null) finshed.onfinish(); } } } } } public void setContentView(int res){ setBackground(new ColorDrawable(Color.WHITE)); LayoutInflater inflater=(LayoutInflater)((Activity)o).getSystemService(Context.LAYOUT_INFLATER_SERVICE); View view=inflater.inflate(res, null); mChildView = view; this.addView(view); ((Activity)o).setContentView(this); } public void setContentView(View view){ setBackground(new ColorDrawable(Color.WHITE)); mChildView = view; this.addView(view); ((Activity)o).setContentView(view); } public void setAutoFinish(boolean finish){ this.autoFinish = finish; } @Override public void onGlobalLayout() { ((Activity)o).getWindow().getDecorView().getWindowVisibleDisplayFrame(rect);//获取显示信息 } interface OnSwipFinshed{ void onfinish(); } /** * 设置滑动结束监听 * @param finshed */ public void setOnSwipFinshed(OnSwipFinshed finshed){ this.finshed = finshed; } interface OnSwiping{ void swiping(MotionEvent event,View parent,View child); } /** * 设置滑动中监听 * @param swiping */ public void setOnSwiping(OnSwiping swiping){ this.swiping = swiping; } public void setTouchLocation(float locationX){ this.locationX = locationX; } }
其中Scroller滚动部分(scrollOrigin scrollRight 借鉴夏安明 http://blog.csdn.net/xiaanming/article/details/20934541 )
效果图:
效果图:
使用方法:
1 在apptheme中添加:<item name="android:windowIsTranslucent">true</item>
2 在对应的activity的onCreat()方法中:SwipBackLayout swipBackLayout = new SwipBackLayout(this); 并用swipBackLayout.setContentView(int res);替代activity的setContentView(int res)
3 提供的有一些监听
4 可以设置是否需要结束监听接口,默认是不需要
5 滑动设置的默认监听范围是边缘到屏幕的十分之一,当然也提供的有对应方法进行修改
之所以选择DecorView->LinearLayout->FrameLayout作为mParent, 是因为在AppCompatActivity中使用sentContView会被包裹在ContentFrameLayout和ActionBarOverLayout中,导致使用默认的actionbar并不能跟随手指滑动。为了滑动销毁的更彻底,也有第三方会将本Layout作为DecorView和LinearLayout的中间层,使之形成 DecorView->SwipLayout->LinearLayout, 然而这种行为在有些第三方rom中会出现错误。
总之就这么多了