原文链接:http://code.taobao.org/p/android-example/diff/46/trunk/%E5%95%86%E5%9F%8E%E8%AF%A6%E6%83%85/src/com
Index: example/shop/DragLayout.java =================================================================== --- example/shop/DragLayout.java (revision 0) +++ example/shop/DragLayout.java (revision 46) @@ -0,0 +1,307 @@ +package com.example.shop; + +import android.annotation.SuppressLint; +import android.content.Context; +import android.support.v4.view.GestureDetectorCompat; +import android.support.v4.view.ViewCompat; +import android.support.v4.widget.ViewDragHelper; +import android.util.AttributeSet; +import android.view.GestureDetector.SimpleOnGestureListener; +import android.view.MotionEvent; +import android.view.View; +import android.view.ViewGroup; + +/** + * viewGroup容器 实现上下两个framelayout拖动切换 + * + * @author 刘伦 + * @version 1.0 + * @date 2016年1月5日 + */ +public class DragLayout extends ViewGroup { + /** + * 拖动工具类 + */ + private final ViewDragHelper mDragHelper; + private GestureDetectorCompat gestureDetectorCompat; + /** + * 上下两个frameLayout,在Activity中注入fragment + */ + private View frameView1, frameView2; + private int viewHeight; + /** + * 滑动速度的阈值,超过这个绝对值认为是上下 + */ + private static final int VEL_THRESHOLD = 100; + /** + * 单位是像素,当上下滑动速度不够时,通过这个阈值来判定是应该粘到顶部还是底部 + */ + private static final int DISTANCE_THRESHOLD = 100; + + /** + * 手指按下,frameView1的gettop + */ + private int downTop1; + + /** + * 手指松开是否加载一下一页的notifier + */ + private ShowNextPageNotifier nextPageNotifier; + + public DragLayout(Context context, AttributeSet attrs) { + this(context, attrs, 0); + } + + public DragLayout(Context context) { + this(context, null); + } + + public DragLayout(Context context, AttributeSet attrs, int defStyle) { + super(context, attrs, defStyle); + mDragHelper = ViewDragHelper + .create(this, 10f, new DragHelperCallback()); + mDragHelper.setEdgeTrackingEnabled(ViewDragHelper.EDGE_BOTTOM); + gestureDetectorCompat = new GestureDetectorCompat(context, + new YScrollDetector()); + } + + @Override + protected void onFinishInflate() { + // 初始化两个view + frameView1 = getChildAt(0); + frameView2 = getChildAt(1); + } + + @Override + public void computeScroll() { + if (mDragHelper.continueSettling(true)) { + ViewCompat.postInvalidateOnAnimation(this); + } + } + @Override + public boolean onInterceptTouchEvent(MotionEvent ev) { + if(frameView1.getBottom()>0&&frameView1.getTop()<0){ + //view在顶部或者底部,正在动画中的时候,不处理touch事件 + return false; + } + boolean yScroll=gestureDetectorCompat.onTouchEvent(ev); + boolean shouldIntercept=mDragHelper.shouldInterceptTouchEvent(ev); + int action=ev.getActionMasked(); + if(action==MotionEvent.ACTION_DOWN){ + //action_down时就让mDragHelper开始工作,否则有时候导致异常 + mDragHelper.processTouchEvent(ev); + downTop1=frameView1.getTop(); + } + return shouldIntercept&&yScroll; + } + @Override + public boolean onTouchEvent(MotionEvent event) { + // 统一交给mDragHelper处理 由DragHelperCallback实现拖动效果 + mDragHelper.processTouchEvent(event); + return true; + } + + @Override + protected void onLayout(boolean changed, int l, int t, int r, int b) { + if (frameView1.getTop() == 0) { + frameView1.layout(l, 0, r, b - t); + frameView2.layout(l, 0, r, b - t); + + viewHeight = frameView1.getMeasuredHeight(); + frameView2.offsetTopAndBottom(viewHeight); + } else { + // 如果已经被初始化,这次onLayout只需将之前的状态存入即可 + frameView1 + .layout(l, frameView1.getTop(), r, frameView1.getBottom()); + frameView2 + .layout(l, frameView2.getTop(), r, frameView2.getBottom()); + } + } + + @SuppressLint("NewApi") + @Override + protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) { + measureChildren(widthMeasureSpec, heightMeasureSpec); + int maxWidth = MeasureSpec.getSize(widthMeasureSpec); + int maxHeight = MeasureSpec.getSize(heightMeasureSpec); + setMeasuredDimension( + resolveSizeAndState(maxWidth, widthMeasureSpec, 0), + resolveSizeAndState(maxHeight, heightMeasureSpec, 0)); + } + + /** + * 这是View的方法,该方法不支持android低版本(2.2、2.3)的操作系统,所以手动复制过来以免强制退出 + */ + public static int resolveSizeAndState(int size, int measureSpec, + int childMeasuredState) { + int result = size; + int specMode = MeasureSpec.getMode(measureSpec); + int specSize = MeasureSpec.getSize(measureSpec); + switch (specMode) { + case MeasureSpec.UNSPECIFIED: + result = size; + break; + case MeasureSpec.AT_MOST: + if (specSize < size) { + result = specSize | MEASURED_STATE_TOO_SMALL; + } else { + result = size; + } + break; + case MeasureSpec.EXACTLY: + result = specSize; + break; + } + + return result | (childMeasuredState & MEASURED_STATE_MASK); + + } + + public void setNextPageListener(ShowNextPageNotifier nextPageNotifier) { + this.nextPageNotifier = nextPageNotifier; + } + + /** + * 手势监听 + * + * @author 刘伦 + * @version 1.0 + * @date 2016年1月5日 + */ + private class YScrollDetector extends SimpleOnGestureListener { + @Override + public boolean onScroll(MotionEvent e1, MotionEvent e2, + float distanceX, float distanceY) { + // 垂直滚动时dy>dx,才被认定是上下拖动 + return Math.abs(distanceY) > Math.abs(distanceX); + } + } + + /** + * 滑动时view位置改变协调处理 + * + * @param viewIndex + * 滑动view的index(1或2) + * @param posTop + * 滑动View的top位置 + */ + private void onViewPosChanged(int viewIndex, int top) { + if (viewIndex == 1) { + int offsetTopBottom = viewHeight + frameView1.getTop() + - frameView2.getTop(); + frameView2.offsetTopAndBottom(offsetTopBottom); + } else if (viewIndex == 2) { + int offsetTopBottom = frameView2.getTop() - viewHeight + - frameView1.getTop(); + frameView1.offsetTopAndBottom(offsetTopBottom); + } + + } + + /** + * 拖拽效果主要逻辑 + * + * @author 刘伦 + * @version 1.0 + * @date 2016年1月5日 + */ + private class DragHelperCallback extends ViewDragHelper.Callback { + + @Override + public void onViewPositionChanged(View changedView, int left, int top, + int dx, int dy) { + int childIndex = 1; + if (changedView == frameView2) { + childIndex = 2; + } + // 一个view位置改变,另一个view的位置要跟进 + onViewPosChanged(childIndex, top); + } + + /** + * 返回值可以决定一个parentview中哪个子view可以拖动 + */ + @Override + public boolean tryCaptureView(View arg0, int arg1) { + // TODO Auto-generated method stub + return true; + } + + @Override + public int getViewVerticalDragRange(View child) { + return 1; + } + + /** + * 滑动松开后 + */ + @Override + public void onViewReleased(View releasedChild, float xvel, float yvel) { + animTopOrBottom(releasedChild, yvel); + } + + @Override + public int clampViewPositionVertical(View child, int top, int dy) { + int finalTop = top; + // 第一个view被拖动 + if (child == frameView1) { + if (top > 0) { + // 不让第一个view网上拖,因为顶部会白板 + finalTop = 0; + } + } else if (child == frameView2) {// 第二个view被拖动 + if (top < 0) { + // 不让第二个view往上拖动,因为底部会白板 + finalTop = 0; + } + } + // finalTop代表的是理论上应该拖动到的位置。此处计算拖动的距离除以一个参数(3), + // 是让滑动的速度变慢。数值越大,滑动的越慢 + return child.getTop() + (finalTop - child.getTop()) / 3; + } + } + + /** + * 滑动松开后,需要向上或者乡下粘到特定的位置 + * + * @param releasedChild + * @param yvel + * @author LiuLun + * @Time 2016年1月7日上午10:50:08 + */ + public void animTopOrBottom(View releasedChild, float yvel) { + // 默认是最顶端 + int finalTop = 0; + if (releasedChild == frameView1) { + // 拖动第一个view松手 + if (yvel < -VEL_THRESHOLD + || (downTop1 == 0 && frameView1.getTop() < -DISTANCE_THRESHOLD)) { + // 向上的速度足够大,就滑动到顶端 + // 向上滑动的距离超过某个值,就滑动到顶端 + finalTop = -viewHeight; + + // 下一页可以初始化了 + if (nextPageNotifier != null) { + nextPageNotifier.onDragNext(); + } + } + } else { + // 拖动第二个view松手 + if (yvel > VEL_THRESHOLD + || (downTop1 == -viewHeight && releasedChild.getTop() > DISTANCE_THRESHOLD)) { + // 保持原地不懂 + finalTop = viewHeight; + } + } + + if (mDragHelper.smoothSlideViewTo(releasedChild, 0, finalTop)) { + ViewCompat.postInvalidateOnAnimation(this); + } + + } + + public interface ShowNextPageNotifier { + public void onDragNext(); + } + +} Index: example/shop/VerticalFragment1.java =================================================================== --- example/shop/VerticalFragment1.java (revision 0) +++ example/shop/VerticalFragment1.java (revision 46) @@ -0,0 +1,22 @@ +package com.example.shop; + +import android.graphics.Paint; +import android.os.Bundle; +import android.support.v4.app.Fragment; +import android.view.LayoutInflater; +import android.view.View; +import android.view.ViewGroup; +import android.widget.TextView; + +public class VerticalFragment1 extends Fragment { + + @Override + public View onCreateView(LayoutInflater inflater, ViewGroup container, + Bundle savedInstanceState) { + View rootView = inflater.inflate(R.layout.vertical_fragment1, null); + TextView oldTextView = (TextView) rootView + .findViewById(R.id.old_textview); + oldTextView.getPaint().setFlags(Paint.STRIKE_THRU_TEXT_FLAG); + return rootView; + } +} Index: example/shop/VerticalFragment2.java =================================================================== --- example/shop/VerticalFragment2.java (revision 0) +++ example/shop/VerticalFragment2.java (revision 46) @@ -0,0 +1,65 @@ +package com.example.shop; + +import android.os.Bundle; +import android.support.v4.app.Fragment; +import android.view.LayoutInflater; +import android.view.View; +import android.view.ViewGroup; +import android.widget.BaseAdapter; +import android.widget.ListAdapter; +import android.widget.ListView; + +public class VerticalFragment2 extends Fragment { + + @Override + public View onCreateView(LayoutInflater inflater, ViewGroup container, + Bundle savedInstanceState) { + View rootView = inflater.inflate(R.layout.vertical_fragment2, null); + initView(rootView); + return rootView; + } + + /** + * 初始化ListView + * + * @param rootView + * 根View + */ + private void initView(View rootView) { + ListView listview = (ListView) rootView + .findViewById(R.id.fragment2_listview); + ListAdapter adapter = new BaseAdapter() { + private LayoutInflater inflater; + + @Override + public View getView(int position, View convertView, ViewGroup parent) { + if (inflater == null) { + inflater = LayoutInflater.from(getActivity()); + } + + if (null == convertView) { + convertView = inflater.inflate( + R.layout.fragment2_list_item, null); + } + return convertView; + } + + @Override + public long getItemId(int position) { + return position; + } + + @Override + public Object getItem(int position) { + return position; + } + + @Override + public int getCount() { + return 100; + } + }; + + listview.setAdapter(adapter); + } +} Index: example/shop/VerticalFragment3.java =================================================================== --- example/shop/VerticalFragment3.java (revision 0) +++ example/shop/VerticalFragment3.java (revision 46) @@ -0,0 +1,31 @@ +package com.example.shop; + +import android.os.Bundle; +import android.support.v4.app.Fragment; +import android.view.LayoutInflater; +import android.view.View; +import android.view.ViewGroup; + +public class VerticalFragment3 extends Fragment { + + private View progressBar; + private CustWebView webview; + private boolean hasInited = false; + + @Override + public View onCreateView(LayoutInflater inflater, ViewGroup container, + Bundle savedInstanceState) { + View rootView = inflater.inflate(R.layout.vertical_fragment3, null); + webview = (CustWebView) rootView.findViewById(R.id.fragment3_webview); + progressBar = rootView.findViewById(R.id.progressbar); + return rootView; + } + + public void initView() { + if (null != webview && !hasInited) { + hasInited = true; + progressBar.setVisibility(View.GONE); + webview.loadUrl("http://m.zol.com/tuan/"); + } + } +} Index: example/shop/MainActivity.java =================================================================== --- example/shop/MainActivity.java (revision 0) +++ example/shop/MainActivity.java (revision 46) @@ -0,0 +1,44 @@ +package com.example.shop; + +import android.os.Bundle; +import android.support.v4.app.FragmentActivity; +import android.view.Window; + +import com.example.shop.DragLayout.ShowNextPageNotifier; + +public class MainActivity extends FragmentActivity { + + private VerticalFragment1 fragment1; + private VerticalFragment3 fragment3; + private DragLayout draglayout; + + @Override + protected void onCreate(Bundle savedInstanceState) { + super.onCreate(savedInstanceState); + requestWindowFeature(Window.FEATURE_NO_TITLE); + setContentView(R.layout.activity_main); + initView(); + } + + /** + * 初始化View + */ + private void initView() { + fragment1 = new VerticalFragment1(); + fragment3 = new VerticalFragment3(); + + getSupportFragmentManager().beginTransaction() + .add(R.id.first, fragment1).add(R.id.second, fragment3) + .commit(); + + ShowNextPageNotifier nextIntf = new ShowNextPageNotifier() { + @Override + public void onDragNext() { + fragment3.initView(); + } + }; + draglayout = (DragLayout) findViewById(R.id.draglayout); + draglayout.setNextPageListener(nextIntf); + } + +} Index: example/shop/CustListView.java =================================================================== --- example/shop/CustListView.java (revision 0) +++ example/shop/CustListView.java (revision 46) @@ -0,0 +1,87 @@ +package com.example.shop; + +import android.content.Context; +import android.util.AttributeSet; +import android.view.MotionEvent; +import android.view.View; +import android.widget.ListView; + +public class CustListView extends ListView { + /** + * 如果是true,则允许拖动至底部的下一页 + */ + private boolean allowDragTop = true; + /** + * 当前点击事件相对于屏幕的y轴 + */ + private float downY = 0; + /** + * 是否需要承包touch事件,needConsumeTouch一旦被定型, 则不会更改 + */ + private boolean needConsumeTouch = true; + + public CustListView(Context context, AttributeSet attrs) { + this(context, attrs, 0); + } + + public CustListView(Context context) { + this(context, null); + } + + public CustListView(Context context, AttributeSet attrs, int defStyle) { + super(context, attrs, defStyle); + } + + @Override + public boolean dispatchTouchEvent(MotionEvent ev) { + if (ev.getAction() == MotionEvent.ACTION_DOWN) { + downY = ev.getRawX(); + // 默认情况下,listview内部滚动优先,默认情况下由该listview去小费touch事件 + needConsumeTouch = true; + allowDragTop = isAtTop(); + } else if (ev.getAction() == MotionEvent.ACTION_MOVE) { + if (!needConsumeTouch) { + // 在最顶端且向上拉了,则这个touch事件交由父类去处理 + getParent().requestDisallowInterceptTouchEvent(false); + return false; + } else if (allowDragTop) { + // needConsumeTouch尚未被定型,此处给起定性 + // 允许拖动到底部的下一页,而且有向上拖动了,就将touch事件交由父view + if (ev.getRawY() - downY > 2) { + // flag设置,由父类去小费 + needConsumeTouch = false; + getParent().requestDisallowInterceptTouchEvent(false); + return false; + } + } + } + // 通知父view是否要处理touch事件 + getParent().requestDisallowInterceptTouchEvent(needConsumeTouch); + return super.dispatchTouchEvent(ev); + } + + /** + * 是否在顶部 + * + * @author LiuLun + * @Time 2016年1月5日下午3:11:13 + */ + private boolean isAtTop() { + boolean resultValue = false; + int childNum = getChildCount(); + + if (childNum == 0) { + // 没child,在顶部 + resultValue = true; + } else { + if (getFirstVisiblePosition() == 0) { + // 根据第一个childView来判定是否在顶部 + View firstView = getChildAt(0); + if (Math.abs(firstView.getTop() - getTop()) < 2) { + resultValue = true; + } + } + } + return resultValue; + } +} Index: example/shop/CustWebView.java =================================================================== --- example/shop/CustWebView.java (revision 0) +++ example/shop/CustWebView.java (revision 46) @@ -0,0 +1,57 @@ +package com.example.shop; + +import android.content.Context; +import android.util.AttributeSet; +import android.view.MotionEvent; +import android.webkit.WebView; + +public class CustWebView extends WebView { + private boolean allowDragTop = true; + private float downY = 0; + private boolean needConsumeTouch = true; + + public CustWebView(Context context, AttributeSet attrs) { + this(context, attrs, 0); + } + + public CustWebView(Context context) { + this(context, null); + } + + public CustWebView(Context context, AttributeSet attrs, int defStyle) { + super(context, attrs, defStyle); + } + + @Override + public boolean dispatchTouchEvent(MotionEvent ev) { + if(ev.getAction()==MotionEvent.ACTION_DOWN){ + downY=ev.getRawY(); + needConsumeTouch=true; + allowDragTop=isAtTop(); + }else if(ev.getAction()==MotionEvent.ACTION_MOVE){ + if(!needConsumeTouch){ + getParent().requestDisallowInterceptTouchEvent(false); + return false; + }else if(allowDragTop){ + if(ev.getRawY()-downY>2){ + needConsumeTouch=false; + getParent().requestDisallowInterceptTouchEvent(false); + return false; + } + } + } + getParent().requestDisallowInterceptTouchEvent(needConsumeTouch); + return super.dispatchTouchEvent(ev); + } + + /** + * 是否在listview顶部 + * + * @return + * @author LiuLun + * @Time 2016年1月5日下午4:07:36 + */ + private boolean isAtTop() { + return getScrollY() == 0; + } +} Index: example/shop/CustScrollView.java =================================================================== --- example/shop/CustScrollView.java (revision 0) +++ example/shop/CustScrollView.java (revision 46) @@ -0,0 +1,72 @@ +package com.example.shop; + +import android.content.Context; +import android.util.AttributeSet; +import android.view.MotionEvent; +import android.widget.ScrollView; + +public class CustScrollView extends ScrollView{ + /** + * 如果是ture,则允许拖动至底部的下一页 + */ + private boolean allowDragBottom=true; + private float downY=0; + /** + * 是否需要承包touch事件,needConsumeTouch一旦被定型,则不会更改 + */ + private boolean needConsumeTouch=true; + /** + * 最大滑动距离 + */ + private int maxScroll=-1; + public CustScrollView(Context context, AttributeSet attrs) { + this(context, attrs,0); + } + + public CustScrollView(Context context) { + this(context,null); + } + public CustScrollView(Context context, AttributeSet attrs, int defStyle) { + super(context, attrs, defStyle); + } + + @Override + public boolean dispatchTouchEvent(MotionEvent ev) { + if(ev.getAction()==MotionEvent.ACTION_DOWN){ + downY=ev.getRawY(); + needConsumeTouch=true; + if(maxScroll>0&&getScrollY()+getMeasuredHeight()>=maxScroll-2){ + //允许向上拖动底部的下一页 + allowDragBottom=true; + }else{ + //不允许向上拖动底部的下一页 + allowDragBottom=false; + } + }else if(ev.getAction()==MotionEvent.ACTION_MOVE){ + if(!needConsumeTouch){ + //在最顶端且向上拉,则这个touch事件交由父类去处理 + getParent().requestDisallowInterceptTouchEvent(false); + return false; + }else if(allowDragBottom){ + //needConsumeTouch尚未被定型,此处给其定型 + //允许拖动到底部的下一页,而且又被向上拖动了,将touch事件交由父view + if(downY-ev.getRawY()>2){ + //flag设置,由父类去小费 + needConsumeTouch=false; + getParent().requestDisallowInterceptTouchEvent(false); + return false; + } + } + } + //通知父view是否要处理touch事件 + getParent().requestDisallowInterceptTouchEvent(needConsumeTouch); + return super.dispatchTouchEvent(ev); + } + @Override + protected void onScrollChanged(int l, int t, int oldl, int oldt) { + if(maxScroll<0){ + maxScroll=computeVerticalScrollRange(); + } + super.onScrollChanged(l, t, oldl, oldt); + } +}