Android 仿淘宝、京东商品详情页向上拖动查看图文详情控件DEMO详解

一、淘宝商品详情页效果

我们的效果

二、实现思路

使用两个scrollView,两个scrollView 竖直排列,通过自定义viewGroup来控制两个scrollView的竖直排列,以及滑动事件的处理。如下图

三、具体实现

1、继承viewGroup自定义布局View 重写onMeasure()和onLayout方法,在onLayout方法中完成对两个子ScrollView的竖直排列布局,代码如下:
布局文件:

<RelativeLayout xmlns:android="http://schemas.android.com/apk/res/android"
 xmlns:tools="http://schemas.android.com/tools"
 android:layout_width="match_parent"
 android:layout_height="match_parent"
 tools:context="com.baoyunlong.view.pulluptoloadmore.MainActivity">
 <com.baoyunlong.view.pulluptoloadmore.PullUpToLoadMore
  android:layout_width="match_parent"
  android:layout_height="match_parent"
  android:orientation="vertical">
  <com.baoyunlong.view.pulluptoloadmore.MyScrollView
   android:layout_width="match_parent"
   android:layout_height="match_parent"
   android:fillViewport="true">
   <LinearLayout
    android:layout_width="match_parent"
    android:layout_height="match_parent"
    android:orientation="vertical">
    <ImageView
     android:scaleType="fitXY"
     android:src="@drawable/a1"
     android:layout_width="match_parent"
     android:layout_height="180dp" />
    <TextView
     android:text="这里是标题"
     android:textSize="18dp"
     android:layout_marginRight="10dp"
     android:layout_marginLeft="10dp"
     android:layout_marginTop="10dp"
     android:layout_width="match_parent"
     android:layout_height="wrap_content" />
    <TextView
     android:layout_marginTop="10dp"
     android:text="子标题"
     android:layout_marginLeft="10dp"
     android:layout_marginRight="10dp"
     android:textSize="18dp"
     android:layout_width="match_parent"
     android:layout_height="wrap_content" />
    ..............
    <LinearLayout
     android:layout_height="0dp"
     android:layout_weight="1"
     android:gravity="bottom"
     android:layout_width="match_parent">
     <TextView
      android:layout_width="match_parent"
      android:layout_height="wrap_content"
      android:height="50dp"
      android:background="#b11"
      android:gravity="center"
      android:text="继续拖动查看图文详情"
      android:textColor="#000" />
    </LinearLayout>
   </LinearLayout>
  </com.baoyunlong.view.pulluptoloadmore.MyScrollView>
  <com.baoyunlong.view.pulluptoloadmore.MyScrollView
   android:layout_width="match_parent"
   android:layout_height="match_parent"
   android:fillViewport="true">
   <LinearLayout
    android:layout_width="match_parent"
    android:layout_height="match_parent"
    android:gravity="center"
    android:orientation="vertical">
    <ImageView
     android:layout_width="wrap_content"
     android:layout_height="wrap_content"
     android:src="@drawable/a1" />
    <ImageView
     android:layout_width="wrap_content"
     android:layout_height="wrap_content"
     android:src="@drawable/a3" />
    .........
   </LinearLayout>
</com.baoyunlong.view.pulluptoloadmore.MyScrollView> </com.baoyunlong.view.pulluptoloadmore.PullUpToLoadMore>
</RelativeLayout> 

代码:

public class PullUpToLoadMore extends ViewGroup {
 public PullUpToLoadMore(Context context) {
  super(context);
 }
 public PullUpToLoadMore(Context context, AttributeSet attrs) {
  super(context, attrs);
 }
 public PullUpToLoadMore(Context context, AttributeSet attrs, int defStyleAttr) {
  super(context, attrs, defStyleAttr);
 }
 @Override
 protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
  super.onMeasure(widthMeasureSpec, heightMeasureSpec);
  measureChildren(widthMeasureSpec, heightMeasureSpec);
 }
 @Override
 protected void onLayout(boolean changed, int l, int t, int r, int b) {
  int childCount = getChildCount();
  int childTop = t;
  for (int i = 0; i < childCount; i++) {
   View child = getChildAt(i);
   child.layout(l, childTop, r, childTop + child.getMeasuredHeight());
   childTop += child.getMeasuredHeight();
  }
 }
} 

2、处理滑动事件

规则如下 :

(1)、当处于第一屏时 第一个ScrollView已经滑动到底部并且滑动方向是往上滑动,这个时候滑动事件应该交给父view处理也就是拦截事件让onInterceptTouchEvent返回true.然后父view通过scrollBy()方法滚动,显示出第二个scrollView。

(2)、当处于第二屏时 第二个ScrollView已经滑动到顶部并且滑动方向是往下滑动,这个时候滑动事件交给父view处理,根据滑动事件显示出第一个ScrollView。

(3)、当手指离开屏幕时,根据滑动速度来决定是回弹到第一个ScrollView还是第二个ScrollView,通过VelocityTracker来获取滑动速度。

3、一些细节的处理

(1)、如果仔细看观察淘宝的实现效果你会发现,当你滑动到刚刚看到 “继续拖动,查看图文详情”的时候,手指抬起,然后再按下重新向上拖动你会发现,第二页并不会划出来,而是停留在了“继续拖动,查看图文详情”的底部,京东的效果也是一样。这样用户体验不太好,我们来优化一下。其实通过查看ScrollView的源码可以看出来,这是因为ScrollView类的onTouchEvent方法的默认实现,调用了parent.requestDisallowInterceptTouchEvent(true)方法 阻止了我们拦截事件,导致我们父view的onInterceptTouchEvent方法无法执行,也就拦截不到事件,拦截不到事件我们的onTouchEvent就无法执行,onTouchEvent无法执行,我们写在onTouchEvent里面的滚动逻辑就执行不到了,导致了上面我们看到的划不动的效果。解决方法就是,我们需要重写dispatchTouchEvent()方法,防止子view干扰我们,这样我们滑动的时候就可以一气呵成了。代码如下:

@Override
 public boolean dispatchTouchEvent(MotionEvent ev) {
  //防止子View禁止父view拦截事件
  this.requestDisallowInterceptTouchEvent(false);
  return super.dispatchTouchEvent(ev);
 } 

(2)、监听ScrollView滑动事件的问题

ScrollView没有提供滚动事件的监听方法,也就没法判断是否滚动到了顶部,或者底部,这里我们继承ScrollView 自己实现滚动事件监听。

/**
 * Created by baoyunlong on 16/6/8.
 */
public class MyScrollView extends ScrollView {
 private static String TAG=MyScrollView.class.getName();
 public void setScrollListener(ScrollListener scrollListener) {
  this.mScrollListener = scrollListener;
 }
 private ScrollListener mScrollListener;
 public MyScrollView(Context context) {
  super(context);
 }
 public MyScrollView(Context context, AttributeSet attrs) {
  super(context, attrs);
 }
 public MyScrollView(Context context, AttributeSet attrs, int defStyleAttr) {
  super(context, attrs, defStyleAttr);
 }
 @Override
 public boolean onTouchEvent(MotionEvent ev) {
  switch (ev.getAction()){
   case MotionEvent.ACTION_MOVE:
    if(mScrollListener!=null){
     int contentHeight=getChildAt(0).getHeight();
     int scrollHeight=getHeight();
     int scrollY=getScrollY();
     mScrollListener.onScroll(scrollY);
     if(scrollY+scrollHeight>=contentHeight||contentHeight<=scrollHeight){
      mScrollListener.onScrollToBottom();
     }else {
      mScrollListener.notBottom();
     }
     if(scrollY==0){
      mScrollListener.onScrollToTop();
     }
    }
    break;
  }
  boolean result=super.onTouchEvent(ev);
  requestDisallowInterceptTouchEvent(false);
  return result;
 }
 public interface ScrollListener{
  void onScrollToBottom();
  void onScrollToTop();
  void onScroll(int scrollY);
  void notBottom();
 } 

4、完整代码如下

/**
 * Created by baoyunlong on 16/6/8.
 */
public class PullUpToLoadMore extends ViewGroup {
 public static String TAG = PullUpToLoadMore.class.getName();
 MyScrollView topScrollView, bottomScrollView;
 VelocityTracker velocityTracker = VelocityTracker.obtain();
 Scroller scroller = new Scroller(getContext());
 int currPosition = 0;
 int position1Y;
 int lastY;
 public int scaledTouchSlop;//最小滑动距离
 int speed = 200;
 boolean isIntercept;
 public boolean bottomScrollVIewIsInTop = false;
 public boolean topScrollViewIsBottom = false;
 public PullUpToLoadMore(Context context) {
  super(context);
  init();
 }
 public PullUpToLoadMore(Context context, AttributeSet attrs) {
  super(context, attrs);
  init();
 }
 public PullUpToLoadMore(Context context, AttributeSet attrs, int defStyleAttr) {
  super(context, attrs, defStyleAttr);
  init();
 }
 private void init() {
  post(new Runnable() {
   @Override
   public void run() {
    topScrollView = (MyScrollView) getChildAt(0);
    bottomScrollView = (MyScrollView) getChildAt(1);
    topScrollView.setScrollListener(new MyScrollView.ScrollListener() {
     @Override
     public void onScrollToBottom() {
      topScrollViewIsBottom = true;
     }
     @Override
     public void onScrollToTop() {
     }
     @Override
     public void onScroll(int scrollY) {
     }
     @Override
     public void notBottom() {
      topScrollViewIsBottom = false;
     }
    });
    bottomScrollView.setScrollListener(new MyScrollView.ScrollListener() {
     @Override
     public void onScrollToBottom() {
     }
     @Override
     public void onScrollToTop() {
     }
     @Override
     public void onScroll(int scrollY) {
      if (scrollY == 0) {
       bottomScrollVIewIsInTop = true;
      } else {
       bottomScrollVIewIsInTop = false;
      }
     }
     @Override
     public void notBottom() {
     }
    });
    position1Y = topScrollView.getBottom();
    scaledTouchSlop = ViewConfiguration.get(getContext()).getScaledTouchSlop();
   }
  });
 }
 @Override
 public boolean dispatchTouchEvent(MotionEvent ev) {
  //防止子View禁止父view拦截事件
  this.requestDisallowInterceptTouchEvent(false);
  return super.dispatchTouchEvent(ev);
 }
 @Override
 public boolean onInterceptTouchEvent(MotionEvent ev) {
  int y = (int) ev.getY();
  switch (ev.getAction()) {
   case MotionEvent.ACTION_DOWN:
    lastY = y;
    break;
   case MotionEvent.ACTION_MOVE:
    //判断是否已经滚动到了底部
    if (topScrollViewIsBottom) {
     int dy = lastY - y;
     //判断是否是向上滑动和是否在第一屏
     if (dy > 0 && currPosition == 0) {
      if (dy >= scaledTouchSlop) {
       isIntercept = true;//拦截事件
       lastY=y;
      }
     }
    }
    if (bottomScrollVIewIsInTop) {
     int dy = lastY - y;
     //判断是否是向下滑动和是否在第二屏
     if (dy < 0 && currPosition == 1) {
      if (Math.abs(dy) >= scaledTouchSlop) {
       isIntercept = true;
      }
     }
    }
    break;
  }
  return isIntercept;
 }
 @Override
 public boolean onTouchEvent(MotionEvent event) {
  int y = (int) event.getY();
  velocityTracker.addMovement(event);
  switch (event.getAction()) {
   case MotionEvent.ACTION_MOVE:
    int dy = lastY - y;
    if (getScrollY() + dy < 0) {
     dy = getScrollY() + dy + Math.abs(getScrollY() + dy);
    }
    if (getScrollY() + dy + getHeight() > bottomScrollView.getBottom()) {
     dy = dy - (getScrollY() + dy - (bottomScrollView.getBottom() - getHeight()));
    }
    scrollBy(0, dy);
    break;
   case MotionEvent.ACTION_UP:
    isIntercept = false;
    velocityTracker.computeCurrentVelocity(1000);
    float yVelocity = velocityTracker.getYVelocity();
    if (currPosition == 0) {
     if (yVelocity < 0 && yVelocity < -speed) {
      smoothScroll(position1Y);
      currPosition = 1;
     } else {
      smoothScroll(0);
     }
    } else {
     if (yVelocity > 0 && yVelocity > speed) {
      smoothScroll(0);
      currPosition = 0;
     } else {
      smoothScroll(position1Y);
     }
    }
    break;
  }
  lastY = y;
  return true;
 }
 @Override
 protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
  super.onMeasure(widthMeasureSpec, heightMeasureSpec);
  measureChildren(widthMeasureSpec, heightMeasureSpec);
 }
 @Override
 protected void onLayout(boolean changed, int l, int t, int r, int b) {
  int childCount = getChildCount();
  int childTop = t;
  for (int i = 0; i < childCount; i++) {
   View child = getChildAt(i);
   child.layout(l, childTop, r, childTop + child.getMeasuredHeight());
   childTop += child.getMeasuredHeight();
  }
 }
 //通过Scroller实现弹性滑动
 private void smoothScroll(int tartY) {
  int dy = tartY - getScrollY();
  scroller.startScroll(getScrollX(), getScrollY(), 0, dy);
  invalidate();
 }
 @Override
 public void computeScroll() {
  if (scroller.computeScrollOffset()) {
   scrollTo(scroller.getCurrX(), scroller.getCurrY());
   postInvalidate();
  }
 }
} 

源码:

github地址

以上所述是小编给大家介绍的Android 仿淘宝、京东商品详情页向上拖动查看图文详情控件DEMO详解,希望对大家有所帮助,如果大家有任何疑问请给我留言,小编会及时回复大家的。在此也非常感谢大家对我们网站的支持!

(0)

相关推荐

  • Android 中通过ViewDragHelper实现ListView的Item的侧拉划出效果

    先来看看,今天要实现的自定义控件效果图: 关于ViewDragHelper的使用,大家可以先看这篇文章ViewDragHelper的使用介绍 实现该自定义控件的大体步骤如下: 1.ViewDragHelper使用的3部曲,初始化ViewDragHelper,传递触摸事件,实现ViewDragHelper.Callback抽象类. 2.需要创建2个直接的子View,分别是前景View和背景View,代表ListView每一项Item的布局的组成,如下所示: 未划出时显示的FrontView: 划出

  • android 添加随意拖动的桌面悬浮窗口

    用过新版本android 360手机助手都人都对 360中只在桌面显示一个小小悬浮窗口羡慕不已吧? 其实实现这种功能,主要有两步: 1.判断当前显示的是为桌面.这个内容我在前面的帖子里面已经有过介绍,如果还没看过的赶快稳步看一下哦. 2.使用windowManager往最顶层添加一个View .这个知识点就是为本文主要讲解的内容哦.在本文的讲解中,我们还会讲到下面的知识点: a.如果获取到状态栏的高度 b.悬浮窗口的拖动 c.悬浮窗口的点击事件 有开始之前,我们先来看一下效果图:  接下来我们来

  • Android编程之控件可拖动的实现方法

    本文实例讲述了Android编程之控件可拖动的实现方法.分享给大家供大家参考,具体如下: 点击和触摸的区别是什么? 点击: 一组动作的集合 手指按下着按钮 手指要在按钮停留一段时间 手指离开按钮 private static final String TAG = "DragViewActivity"; private ImageView iv_dv_view; private TextView tv_drag_view; private int startx; private int

  • Android使用ViewDragHelper实现QQ6.X最新版本侧滑界面效果实例代码

    (一).前言: 这两天QQ进行了重大更新(6.X)尤其在UI风格上面由之前的蓝色换成了白色居多了,侧滑效果也发生了一些变化,那我们今天来模仿实现一个QQ6.X版本的侧滑界面效果.今天我们还是采用神器ViewDragHelper来实现. 本次实例具体代码已经上传到下面的项目中,欢迎各位去star和fork一下. https://github.com/jiangqqlmj/DragHelper4QQ FastDev4Android框架项目地址:https://github.com/jiangqqlm

  • Android ViewDragHelper完全解析 自定义ViewGroup神器

    一.概述 在自定义ViewGroup中,很多效果都包含用户手指去拖动其内部的某个View(eg:侧滑菜单等),针对具体的需要去写好onInterceptTouchEvent和onTouchEvent这两个方法是一件很不容易的事,需要自己去处理:多手指的处理.加速度检测等等. 好在官方在v4的支持包中提供了ViewDragHelper这样一个类来帮助我们方便的编写自定义ViewGroup.简单看一下它的注释: ViewDragHelper is a utility class for writin

  • Android ViewDragHelper仿淘宝拖动加载效果

    拖动加载是我在淘宝的商品详情界面发现的,感觉很实用.于是就分析它的实现方式,感觉用ViewDragHelper可以很方便的实现这种效果.下面大致把我的思路分步骤写一下.先上图吧. 首先建工程什么的我就不多说了.咱从ViewDragHelper的实现开始说吧,ViewDragHelper一般用在一个自定义ViewGroup的内部,可以对其子View进行移动操作. 创建自定义ViewGroup: package com.maxi.viewdraghelpertest.widget; import a

  • Android ViewDragHelper使用介绍

    ViewDragHelper是support.v4下提供的用于处理拖拽滑动的辅助类,查看Android的DrawerLayout源码,可以发现,它内部就是使用了该辅助类来处理滑动事件的. public DrawerLayout(Context context, AttributeSet attrs, int defStyle) { super(context, attrs, defStyle); setDescendantFocusability(ViewGroup.FOCUS_AFTER_DE

  • Android 可拖动的seekbar自定义进度值

    最近接了个项目其中有需要要实现此功能:seekbar需要显示最左和最右值,进度要跟随进度块移动.下面通过此图给大家展示下效果,可能比文字描述要更清晰. 其实实现起来很简单,主要是思路.自定义控件的话也不难,之前我的博客也有专门介绍,这里就不再多说. 实现方案 这里是通过继承seekbar来自定义控件,这样的方式最快.主要难点在于进度的显示,其实我很的是最笨的方法,就是用了一个popwindow显示在进度条的上方,然后在移动滑块的时候实时的改变它显示的横坐标.看进度显示的核心代码: private

  • Android使用ViewDragHelper实现仿QQ6.0侧滑界面(一)

    QQ是大家离不开的聊天工具,方便既实用,自从qq更新至6.0之后,侧滑由原来的划出后主面板缩小变成了左右平滑,在外观上有了很大的提升,于是我就是尝试理解下里面的各种逻辑,结合相关资料,研究研究. 知道这里面的一个主要类是ViewDragHelper,那么首先我们要先来了解一下这个ViewDragHelper类,正所谓打蛇打七寸,我们就先来看看官方文档怎么介绍的,有什么奇特的功能. 首先继承: java.lang.Object android.support.v4.widget.ViewDragH

  • Android基于ViewDragHelper仿QQ5.0侧滑界面效果

    QQ5.0侧滑效果实现方案有很多方式,今天我们使用ViewDragHelper来实现一下. 先上效果图: ①自定义控件SlidingMenu继承FrameLayout,放在FrameLayout上面的布局一层叠着者一层,通过getChildAt()可以很方便的获取到任意一层,进而控制此布局的变化. public class SlidingMenu extends FrameLayout { private ViewDragHelper mViewDragHelper; private int m

随机推荐