t型网站域名和版面,wordpress 图像小工具,网站导航菜单代码,网站流量提升方案仿照网易严选商品详情页面#xff0c;整个页面分为两个部分#xff0c;上面一部分是Native的ScrollView#xff0c;下面一部分则是WebView#xff0c;其目的是为了可以进行分步加载。滑动到ScrollView底部时#xff0c;继续向上拖动#xff0c;可以加载下面的WebView部分…仿照网易严选商品详情页面整个页面分为两个部分上面一部分是Native的ScrollView下面一部分则是WebView其目的是为了可以进行分步加载。滑动到ScrollView底部时继续向上拖动可以加载下面的WebView部分。反之滑动到WebView顶部时继续向下拖动可以展示上面的ScrollView部分。其中在向上或者向下拖动的时候增加了一些阻力。另外还使用了自定义控件辅助神器ViewDragHelper可以使滑动比较流畅。一、自定义View总体的实现思路是对ScrollView和WebView的dispatchTouchEvent 方法进行重写当在ScrollView的顶部并且向上拉或者是在WebView的底部向下拉时自身不消费事件让父容器拦截事件并处理父容器Touch事件的拦截与处理都交给ViewDragHelper来处理。1.自定义ViewGrouppublic class GoodsDetailVerticalSlideView extends ViewGroup {private static final int VEL_THRESHOLD 6000;// 滑动速度的阈值超过这个绝对值认为是上下private int DISTANCE_THRESHOLD 75;// 单位是dp当上下滑动速度不够时通过这个阈值来判定是应该粘到顶部还是底部private OnPullListener onPullListener;// 页面上拉或者下拉监听器private OnShowPreviousPageListener onShowPreviousPageListener;// 手指松开是否加载上一页的监听器private OnShowNextPageListener onShowNextPageListener; // 手指松开是否加载下一页的监听器private ViewDragHelper mDragHelper;private GestureDetectorCompat mGestureDetector;// 手势识别处理手指在触摸屏上的滑动private View view1;private View view2;private int viewHeight;private int currentPage;// 当前第几页private int pageIndex;// 页码标记/*** 设置页面上拉或者下拉监听* param onPullListener*/public void setOnPullListener(OnPullListener onPullListener) {this.onPullListener onPullListener;}/*** 设置加载上一页监听* param onShowPreviousPageListener*/public void setOnShowPreviousPageListener(OnShowPreviousPageListener onShowPreviousPageListener) {this.onShowPreviousPageListener onShowPreviousPageListener;}/*** 设置加载下一页监听* param onShowNextPageListener*/public void setOnShowNextPageListener(OnShowNextPageListener onShowNextPageListener) {this.onShowNextPageListener onShowNextPageListener;}public GoodsDetailVerticalSlideView(Context context) {this(context, null);}public GoodsDetailVerticalSlideView(Context context, AttributeSet attrs) {this(context, attrs, 0);}public GoodsDetailVerticalSlideView(Context context, AttributeSet attrs, int defStyleAttr) {super(context, attrs, defStyleAttr);DISTANCE_THRESHOLD (int) TypedValue.applyDimension(TypedValue.COMPLEX_UNIT_DIP, DISTANCE_THRESHOLD, getResources().getDisplayMetrics());// 在自定义ViewGroup时ViewDragHelper可以用来拖拽和设置子View的位置(在ViewGroup范围内)。mDragHelper ViewDragHelper.create(this, 10.0f, new DragCallBack());mDragHelper.setEdgeTrackingEnabled(ViewDragHelper.EDGE_BOTTOM);mGestureDetector new GestureDetectorCompat(getContext(), new YScrollDetector());currentPage 1;}Overrideprotected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {measureChildren(widthMeasureSpec, heightMeasureSpec);super.onMeasure(widthMeasureSpec, heightMeasureSpec);}Overrideprotected void onLayout(boolean changed, int l, int t, int r, int b) {if (view1 null) view1 getChildAt(0);if (view2 null) view2 getChildAt(1);//当滑到第二页时第二页的top为0第一页为负数。if (view1.getTop() 0) {view1.layout(0, 0, r, b);view2.layout(0, 0, r, b);viewHeight view1.getMeasuredHeight();view2.offsetTopAndBottom(viewHeight);// view2向下移动到view1的底部} else {view1.layout(view1.getLeft(), view1.getTop(), view1.getRight(), view1.getBottom());view2.layout(view2.getLeft(), view2.getTop(), view2.getRight(), view2.getBottom());}}Overrideprotected void onSizeChanged(int w, int h, int oldw, int oldh) {super.onSizeChanged(w, h, oldw, oldh);}// Touch事件的拦截与处理都交给mDragHelper来处理Overridepublic boolean onInterceptTouchEvent(MotionEvent ev) {if (view1.getBottom() 0 view1.getTop() 0) {// view粘到顶部或底部正在动画中的时候不处理Touch事件return false;}boolean shouldIntercept false;boolean yScroll mGestureDetector.onTouchEvent(ev);try {shouldIntercept mDragHelper.shouldInterceptTouchEvent(ev);//修复导致OnTouchEvent中pointerIndex out of range的异常int action ev.getActionMasked();if (action MotionEvent.ACTION_DOWN) {mDragHelper.processTouchEvent(ev);}} catch (Exception e) {e.printStackTrace();}return shouldIntercept yScroll;}Overridepublic boolean onTouchEvent(MotionEvent event) {try {mDragHelper.processTouchEvent(event);} catch (Exception e) {e.printStackTrace();}return true;}private class DragCallBack extends ViewDragHelper.Callback {Overridepublic boolean tryCaptureView(View child, int pointerId) {// 两个子View都需要跟踪返回truereturn true;}Overridepublic void onViewPositionChanged(View changedView, int left, int top, int dx, int dy) {// 由于拖拽导致被捕获View的位置发生改变时进行回调if (changedView view1) {view2.offsetTopAndBottom(dy);if (onPullListener ! null currentPage 1) {onPullListener.onPull(1, top);}}if (changedView view2) {view1.offsetTopAndBottom(dy);if (onPullListener ! null currentPage 2) {onPullListener.onPull(2, top);}}// 如果不重绘拖动的时候其他View会不显示ViewCompat.postInvalidateOnAnimation(GoodsDetailVerticalSlideView.this);}Overridepublic int getViewVerticalDragRange(View child) {// 这个用来控制拖拽过程中松手后自动滑行的速度return child.getHeight();}Overridepublic void onViewReleased(View releasedChild, float xvel, float yvel) {// 滑动松开后需要向上或者向下粘到特定的位置, 默认是粘到最顶端int finalTop 0;if (releasedChild view1) {// 拖动view1松手if (yvel -VEL_THRESHOLD || releasedChild.getTop() -DISTANCE_THRESHOLD) {// 向上的速度足够大或者向上滑动的距离超过某个阈值就滑动到view2顶端finalTop -viewHeight;}} else {// 拖动view2松手if (yvel VEL_THRESHOLD || releasedChild.getTop() DISTANCE_THRESHOLD) {// 向下的速度足够大或者向下滑动的距离超过某个阈值就滑动到view1顶端finalTop viewHeight;}}//触发缓慢滚动//将给定子View平滑移动到给定位置会回调continueSettling(boolean)方法在内部是用的ScrollerCompat来实现滑动的。//如果返回true表明动画应该继续所以调用者应该调用continueSettling(boolean)在每个后续帧继续动作直到它返回false。if (mDragHelper.smoothSlideViewTo(releasedChild, 0, finalTop)) {ViewCompat.postInvalidateOnAnimation(GoodsDetailVerticalSlideView.this);}}Overridepublic int clampViewPositionVertical(View child, int top, int dy) {// 限制被拖动的子View在垂直方向的移动可以用作边界约束// 阻尼滑动让滑动位移变为1/2除数越大阻力越大return child.getTop() dy / 2;}}Overridepublic void computeScroll() {// 判断smoothSlideViewTo触发的continueSettling(boolean)的返回值if (mDragHelper.continueSettling(true)) {// 如果当前被捕获的子View还需要继续移动则进行重绘直到它返回false返回false表示不用后续操作就能完成这个动作了。ViewCompat.postInvalidateOnAnimation(this);if (view2.getTop() 0) {currentPage 2;if (onShowNextPageListener ! null pageIndex ! 2) {onShowNextPageListener.onShowNextPage();pageIndex 2;}} else if (view1.getTop() 0) {currentPage 1;if (onShowPreviousPageListener ! null pageIndex ! 1) {onShowPreviousPageListener.onShowPreviousPage();pageIndex 1;}}}}/** 滚动到view1顶部 */public void smoothSlideToFirstPageTop() {if (currentPage 2) {//触发缓慢滚动if (mDragHelper.smoothSlideViewTo(view2, 0, viewHeight)) {ViewCompat.postInvalidateOnAnimation(this);}}}/** 滚动到view2顶部 */public void smoothSlideToSecondPageTop() {if (currentPage 1) {//触发缓慢滚动if (mDragHelper.smoothSlideViewTo(view1, 0, -viewHeight)) {ViewCompat.postInvalidateOnAnimation(this);}}}private class YScrollDetector extends GestureDetector.SimpleOnGestureListener {Overridepublic boolean onScroll(MotionEvent e1, MotionEvent e2, float dx, float dy) {// 垂直滑动时dydx才被认定是上下拖动return Math.abs(dy) Math.abs(dx);}}public interface OnPullListener{void onPull(int currentPage, int top);}public interface OnShowPreviousPageListener{void onShowPreviousPage();}public interface OnShowNextPageListener {void onShowNextPage();}}其中有一些回调监听Listener在具体业务逻辑处理的时候可以跟Activity进行相应的交互其余部分基本都有代码注释了。2.自定义ScrollViewpublic class GoodsDetailScrollView extends ScrollView {private float downX;private float downY;public GoodsDetailScrollView(Context context) {this(context, null);}public GoodsDetailScrollView(Context context, AttributeSet attrs) {this(context, attrs, 0);}public GoodsDetailScrollView(Context context, AttributeSet attrs, int defStyleAttr) {super(context, attrs, defStyleAttr);}Overridepublic boolean dispatchTouchEvent(MotionEvent ev) {switch (ev.getAction()) {case MotionEvent.ACTION_DOWN:downX ev.getX();downY ev.getY();//如果滑动到了最底部就允许继续向上滑动加载下一页否者不允许getParent().requestDisallowInterceptTouchEvent(true);break;case MotionEvent.ACTION_MOVE:float dx ev.getX() - downX;float dy ev.getY() - downY;boolean allowParentTouchEvent;if (Math.abs(dy) Math.abs(dx)) {if (dy 0) {//ScrollView顶部下拉时需要放大图片自身消费事件allowParentTouchEvent false;} else {//位于底部时上拉让父View消费事件allowParentTouchEvent isBottom();}} else {//水平方向滑动自身消费事件allowParentTouchEvent false;}getParent().requestDisallowInterceptTouchEvent(!allowParentTouchEvent);}return super.dispatchTouchEvent(ev);}public boolean isTop() {return !canScrollVertically(-1);}public boolean isBottom() {return !canScrollVertically(1);}public void goTop() {scrollTo(0, 0);}}其中可以根据自身业务逻辑的需要对dispatchTouchEvent事件分发做相应的调整。3.自定义WebViewpublic class GoodsDetailWebView extends WebView {private float downX;private float downY;public GoodsDetailWebView(Context context) {this(context, null);}public GoodsDetailWebView(Context context, AttributeSet attrs) {this(context, attrs, 0);}public GoodsDetailWebView(Context context, AttributeSet attrs, int defStyleAttr) {super(context, attrs, defStyleAttr);}Overridepublic boolean dispatchTouchEvent(MotionEvent ev) {switch (ev.getAction()) {case MotionEvent.ACTION_DOWN:downX ev.getX();downY ev.getY();//如果滑动到了最顶部就允许继续向下滑动加载上一页否者不允许getParent().requestDisallowInterceptTouchEvent(true);break;case MotionEvent.ACTION_MOVE:float dx ev.getX() - downX;float dy ev.getY() - downY;boolean allowParentTouchEvent;if (Math.abs(dy) Math.abs(dx)) {if (dy 0) {//位于顶部时下拉让父View消费事件allowParentTouchEvent isTop();} else {//向上滑动自身消费事件allowParentTouchEvent false;}} else {//水平方向滑动自身消费事件allowParentTouchEvent false;}getParent().requestDisallowInterceptTouchEvent(!allowParentTouchEvent);}return super.dispatchTouchEvent(ev);}public boolean isTop() {return getScrollY() 0;}public boolean isBottom() {return getHeight() getScrollY() getContentHeight() * getScale();}public void goTop() {scrollTo(0, 0);}}同样的可以根据自身业务逻辑的需要对dispatchTouchEvent事件分发做相应的调整。二、如何使用1.在Activity中使用GoodsDetailVerticalSlideView控件(1)使用GoodsDetailVerticalSlideView控件内部包含两个子View分别表示第一部分ScrollView和第二部分WebView可以先使用FrameLayout占位然后在代码中使用Fragment替换。android:layout_widthmatch_parentandroid:layout_heightmatch_parentandroid:backgroundcolor/whiteandroid:idid/goods_detail_vertical_slide_viewandroid:layout_widthmatch_parentandroid:layout_heightmatch_parentandroid:idid/layout_goods_scrollviewandroid:layout_widthmatch_parentandroid:layout_heightmatch_parent/android:idid/layout_goods_webviewandroid:layout_widthmatch_parentandroid:layout_heightmatch_parent/(2)当然也可以直接使用GoodsDetailScrollView和GoodsDetailWebViewandroid:layout_widthmatch_parentandroid:layout_heightmatch_parentandroid:backgroundcolor/whiteandroid:idid/goods_detail_vertical_slide_viewandroid:layout_widthmatch_parentandroid:layout_heightmatch_parentandroid:layout_widthmatch_parentandroid:layout_heightmatch_parent......android:layout_widthmatch_parentandroid:layout_heightmatch_parent......2.在Fragment中使用GoodsDetailScrollView和GoodsDetailWebView这边有个注意点就是上拉或者下拉的时候我们一般都会给用户展示一个文字和图片的指示器来提示用户如何操作我们只需要把指示器放在上面一部分的ScrollView布局里面即可然后根据目前正在展示哪一部分进行显示/隐藏以及文字图片变化就可以了这样可以使我们的整个拖动效果看起来比较流畅。(1)Fragment中使用GoodsDetailScrollViewandroid:idid/goods_detail_scrollviewandroid:layout_widthmatch_parentandroid:layout_heightwrap_contentandroid:orientationverticalandroid:layout_widthmatch_parentandroid:layout_heightwrap_contentandroid:orientationvertical......