原文链接: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);
+ }
+}