Android 仿淘宝京东商品详情页阻力翻页效果

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


  • 1
    点赞
  • 5
    收藏
    觉得还不错? 一键收藏
  • 1
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值