仿照网易严选商品详情页面,整个页面分为两个部分,上面一部分是Native的ScrollView,下面一部分则是WebView,其目的是为了可以进行分步加载。滑动到ScrollView底部时,继续向上拖动,可以加载下面的WebView部分。反之,滑动到WebView顶部时,继续向下拖动,可以展示上面的ScrollView部分。其中,在向上或者向下拖动的时候,增加了一些阻力。另外,还使用了自定义控件辅助神器ViewDragHelper,可以使滑动比较流畅。

一、自定义View

总体的实现思路是对ScrollView和WebView的dispatchTouchEvent 方法进行重写,当在ScrollView的顶部并且向上拉,或者是在WebView的底部向下拉时,自身不消费事件,让父容器拦截事件并处理,父容器Touch事件的拦截与处理都交给ViewDragHelper来处理。

1.自定义ViewGroup

public 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;

}

@Override

protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {

measureChildren(widthMeasureSpec, heightMeasureSpec);

super.onMeasure(widthMeasureSpec, heightMeasureSpec);

}

@Override

protected 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());

}

}

@Override

protected void onSizeChanged(int w, int h, int oldw, int oldh) {

super.onSizeChanged(w, h, oldw, oldh);

}

// Touch事件的拦截与处理都交给mDragHelper来处理

@Override

public 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;

}

@Override

public boolean onTouchEvent(MotionEvent event) {

try {

mDragHelper.processTouchEvent(event);

} catch (Exception e) {

e.printStackTrace();

}

return true;

}

private class DragCallBack extends ViewDragHelper.Callback {

@Override

public boolean tryCaptureView(View child, int pointerId) {

// 两个子View都需要跟踪,返回true

return true;

}

@Override

public 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);

}

@Override

public int getViewVerticalDragRange(View child) {

// 这个用来控制拖拽过程中松手后,自动滑行的速度

return child.getHeight();

}

@Override

public 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);

}

}

@Override

public int clampViewPositionVertical(View child, int top, int dy) {

// 限制被拖动的子View在垂直方向的移动,可以用作边界约束

// 阻尼滑动,让滑动位移变为1/2,除数越大阻力越大

return child.getTop() + dy / 2;

}

}

@Override

public 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 {

@Override

public boolean onScroll(MotionEvent e1, MotionEvent e2, float dx, float dy) {

// 垂直滑动时dy>dx,才被认定是上下拖动

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.自定义ScrollView

public 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);

}

@Override

public 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.自定义WebView

public 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);

}

@Override

public 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_width="match_parent"

android:layout_height="match_parent"

android:background="@color/white">

android:id="@+id/goods_detail_vertical_slide_view"

android:layout_width="match_parent"

android:layout_height="match_parent">

android:id="@+id/layout_goods_scrollview"

android:layout_width="match_parent"

android:layout_height="match_parent"/>

android:id="@+id/layout_goods_webview"

android:layout_width="match_parent"

android:layout_height="match_parent"/>

(2)当然,也可以直接使用GoodsDetailScrollView和GoodsDetailWebView

android:layout_width="match_parent"

android:layout_height="match_parent"

android:background="@color/white">

android:id="@+id/goods_detail_vertical_slide_view"

android:layout_width="match_parent"

android:layout_height="match_parent">

android:layout_width="match_parent"

android:layout_height="match_parent">

......

android:layout_width="match_parent"

android:layout_height="match_parent">

......

2.在Fragment中使用GoodsDetailScrollView和GoodsDetailWebView

这边有个注意点就是上拉或者下拉的时候,我们一般都会给用户展示一个文字和图片的指示器来提示用户如何操作,我们只需要把指示器放在上面一部分的ScrollView布局里面即可,然后根据目前正在展示哪一部分进行显示/隐藏以及文字图片变化就可以了,这样可以使我们的整个拖动效果看起来比较流畅。

(1)Fragment中使用GoodsDetailScrollView

android:id="@+id/goods_detail_scrollview"

android:layout_width="match_parent"

android:layout_height="wrap_content"

android:orientation="vertical">

android:layout_width="match_parent"

android:layout_height="wrap_content"

android:orientation="vertical">

......

android 网易item广告,Android仿网易严选商品详情页相关推荐

  1. Android开发之仿淘宝商品详情页

    看到有人在问如何实现淘宝商品详情页效果,手痒了就撸了一个,献上效果图 大致梳理一下思路,这里不提供源码 状态栏透明使用开源库StatusBarCompat,为了兼容手机4.4 dependencies ...

  2. Android 仿淘宝商品详情页下拉足迹Demo

    DropDownMultiPager 仿淘宝等商品详情页下拉足迹效果SimpleDemo 可colne之后看MainActivity的调用,方便二次开发 依赖 compile 'com.nineold ...

  3. 仿京东天猫商品详情页

    1.参考借鉴 Android仿京东商品详情页上拉查看图文详情 [模仿淘宝.京东.蘑菇街商品详情页,可嵌套ListView.WebView.ViewPager.FragmentTabhost等](htt ...

  4. 仿淘宝商品详情页TabLayout+ListView

    第一次写博客,我是一名Android的小码农写代码也有三四年了.有点好玩的跟大家分享一下 项目对商品详情页改版有新需求.顶部是一个渐变的Title包括"宝贝","详情&q ...

  5. 仿淘宝商品详情页中(继续拖动到图文详情)

    参考文章http://www.jb51.net/article/91858.htm 核心view 有2个 一个是自定义的ViewGroup 一个是自定义的ScrollView 首先是自定义的Scrol ...

  6. 【商城开发三】Android 仿淘宝商品详情页下拉足迹修改版

    开发商城的快有半个月了,需要做到详情页下拉足迹的效果,网上找了找没找到,找到一个差不多还有点问题,然后在基础上进行了二次开发 感谢http://blog.csdn.net/yaphetzhao/art ...

  7. 仿淘宝商品详情页顶部banner和沉浸式效果

    这边使用第三方沉浸式库https://github.com/gyf-dev/ImmersionBar 使用自己去研究 一.沉浸式效果 主要是看滑动过程中toolBar的透明度变化 布局如下: < ...

  8. 仿淘宝商品详情页图片滑动并且数字也跟着变化

    今天遇到需求需要做个淘宝那样的商品详情页如图(这里只差放图片了)支持移动端,当然用的是swiper.js支持左右滑动 上代码 html代码 <div class="swiper-con ...

  9. 仿淘宝商品详情页[带有视频和图片的轮播功能]

    因为工作需求的原因,自己写了一个demo,既实现了功能,又能与大家分享,很高兴!Demo已上传GitHub,https://github.com/xinniangdeweidao/CloneTaoba ...

最新文章

  1. js parsefloat
  2. Intel VT学习笔记(四)—— VMCS(下)
  3. 浏览器卡怎么办_SD卡无法格式化怎么修复?简单修复方法介绍
  4. 《嵌入式Linux软硬件开发详解——基于S5PV210处理器》——2.2 DDR2 SDRAM芯片
  5. 云联惠认证时间_云联惠最新消息2018 云联惠2018年最新消息
  6. c++编码规范_汽车嵌入式软件测试——嵌入式软件标准及规范简介
  7. ssh连接Linux很慢,且ssh传输文件很慢的解决方案
  8. 音乐u盘排序软件_传输数据快速的各种U盘系列 定制U盘
  9. P2915 [USACO08NOV] Mixed Up Cows
  10. 异常处理-try catch
  11. eXtremeComponents介绍
  12. 四、回归分析之线性回归模型构建
  13. 程序员如何阅读英文资料
  14. 消防产品在酒店行业的应用
  15. 计算机正朝两级方向发展即,当前计算机正朝两极方向发展,即()。A、专用机和通用机B、微型机和巨型机C、模拟机和数字机D、个人...
  16. Python获取指定时间范围内的工作日、假日日、法定节假日
  17. 【PHP-网页内容抓取】抓取网页内容的两种常用方法
  18. 阿里云轻量服务器windows系统远程桌面无法连接?
  19. html 样式重叠问题,css怎么解决网页重叠问题
  20. TransMVSNet阅读笔记

热门文章

  1. 16福师计算机应用基础在线作业,16春季福师《计算机应用基础》在线作业二.doc...
  2. 汽车电子专业知识篇(十五)-整车电气系统设计——高压系统集成方案
  3. linux修改文件内容_详解5种实用方法---Linux系统清空或删除大文件内容
  4. 比较二进制_浮点数比较的精度问题
  5. uni-app动态绑定class和style
  6. webpack联邦模块之webpack运行时
  7. 关于background-*的一些属性
  8. 【Python】[02]初识Python
  9. python eval 用法
  10. Selenium WebDriver + python 自动化测试框架