Android仿淘宝商品浏览界面图片滚动效果

用手机淘宝浏览商品详情时,商品图片是放在后面的,在第一个ScrollView滚动到最底下时会有提示,继续拖动才能浏览图片。仿照这个效果写一个出来并不难,只要定义一个Layout管理两个ScrollView就行了,当第一个ScrollView滑到底部时,再次向上滑动进入第二个ScrollView。效果如下:

需要注意的地方是:

1、如果是手动滑到底部需要再次按下才能继续往下滑,自动滚动到底部则不需要

2、在由上一个ScrollView滑动到下一个ScrollView的过程中多只手指相继拖动也不会导致布局的剧变,也就是多个pointer的滑动不会导致move距离的剧变。

这个Layout的实现思路是:

在布局中放置两个ScrollView,并为其设置OnTouchListener,时刻判断ScrollView的滚动距离,一旦第一个ScrollView滚动到底部,则标识改为可向上拖动,此时开始记录滑动距离mMoveLen,根据mMoveLen重新layout两个ScrollView;同理,监听第二个ScrollView是否滚动到顶部,以往下拖动。

OK,明白了原理之后可以看代码了:

package com.jingchen.tbviewer; 

import java.util.Timer;
import java.util.TimerTask; 

import android.content.Context;
import android.os.Handler;
import android.os.Message;
import android.util.AttributeSet;
import android.view.MotionEvent;
import android.view.VelocityTracker;
import android.view.View;
import android.widget.RelativeLayout;
import android.widget.ScrollView; 

/**
 * 包含两个ScrollView的容器
 *
 * @author chenjing
 *
 */
public class ScrollViewContainer extends RelativeLayout { 

  /**
   * 自动上滑
   */
  public static final int AUTO_UP = 0;
  /**
   * 自动下滑
   */
  public static final int AUTO_DOWN = 1;
  /**
   * 动画完成
   */
  public static final int DONE = 2;
  /**
   * 动画速度
   */
  public static final float SPEED = 6.5f; 

  private boolean isMeasured = false; 

  /**
   * 用于计算手滑动的速度
   */
  private VelocityTracker vt; 

  private int mViewHeight;
  private int mViewWidth; 

  private View topView;
  private View bottomView; 

  private boolean canPullDown;
  private boolean canPullUp;
  private int state = DONE; 

  /**
   * 记录当前展示的是哪个view,0是topView,1是bottomView
   */
  private int mCurrentViewIndex = 0;
  /**
   * 手滑动距离,这个是控制布局的主要变量
   */
  private float mMoveLen;
  private MyTimer mTimer;
  private float mLastY;
  /**
   * 用于控制是否变动布局的另一个条件,mEvents==0时布局可以拖拽了,mEvents==-1时可以舍弃将要到来的第一个move事件,
   * 这点是去除多点拖动剧变的关键
   */
  private int mEvents; 

  private Handler handler = new Handler() { 

    @Override
    public void handleMessage(Message msg) {
      if (mMoveLen != 0) {
        if (state == AUTO_UP) {
          mMoveLen -= SPEED;
          if (mMoveLen <= -mViewHeight) {
            mMoveLen = -mViewHeight;
            state = DONE;
            mCurrentViewIndex = 1;
          }
        } else if (state == AUTO_DOWN) {
          mMoveLen += SPEED;
          if (mMoveLen >= 0) {
            mMoveLen = 0;
            state = DONE;
            mCurrentViewIndex = 0;
          }
        } else {
          mTimer.cancel();
        }
      }
      requestLayout();
    } 

  }; 

  public ScrollViewContainer(Context context) {
    super(context);
    init();
  } 

  public ScrollViewContainer(Context context, AttributeSet attrs) {
    super(context, attrs);
    init();
  } 

  public ScrollViewContainer(Context context, AttributeSet attrs, int defStyle) {
    super(context, attrs, defStyle);
    init();
  } 

  private void init() {
    mTimer = new MyTimer(handler);
  } 

  @Override
  public boolean dispatchTouchEvent(MotionEvent ev) {
    switch (ev.getActionMasked()) {
    case MotionEvent.ACTION_DOWN:
      if (vt == null)
        vt = VelocityTracker.obtain();
      else
        vt.clear();
      mLastY = ev.getY();
      vt.addMovement(ev);
      mEvents = 0;
      break;
    case MotionEvent.ACTION_POINTER_DOWN:
    case MotionEvent.ACTION_POINTER_UP:
      // 多一只手指按下或抬起时舍弃将要到来的第一个事件move,防止多点拖拽的bug
      mEvents = -1;
      break;
    case MotionEvent.ACTION_MOVE:
      vt.addMovement(ev);
      if (canPullUp && mCurrentViewIndex == 0 && mEvents == 0) {
        mMoveLen += (ev.getY() - mLastY);
        // 防止上下越界
        if (mMoveLen > 0) {
          mMoveLen = 0;
          mCurrentViewIndex = 0;
        } else if (mMoveLen < -mViewHeight) {
          mMoveLen = -mViewHeight;
          mCurrentViewIndex = 1; 

        }
        if (mMoveLen < -8) {
          // 防止事件冲突
          ev.setAction(MotionEvent.ACTION_CANCEL);
        }
      } else if (canPullDown && mCurrentViewIndex == 1 && mEvents == 0) {
        mMoveLen += (ev.getY() - mLastY);
        // 防止上下越界
        if (mMoveLen < -mViewHeight) {
          mMoveLen = -mViewHeight;
          mCurrentViewIndex = 1;
        } else if (mMoveLen > 0) {
          mMoveLen = 0;
          mCurrentViewIndex = 0;
        }
        if (mMoveLen > 8 - mViewHeight) {
          // 防止事件冲突
          ev.setAction(MotionEvent.ACTION_CANCEL);
        }
      } else
        mEvents++;
      mLastY = ev.getY();
      requestLayout();
      break;
    case MotionEvent.ACTION_UP:
      mLastY = ev.getY();
      vt.addMovement(ev);
      vt.computeCurrentVelocity(700);
      // 获取Y方向的速度
      float mYV = vt.getYVelocity();
      if (mMoveLen == 0 || mMoveLen == -mViewHeight)
        break;
      if (Math.abs(mYV) < 500) {
        // 速度小于一定值的时候当作静止释放,这时候两个View往哪移动取决于滑动的距离
        if (mMoveLen <= -mViewHeight / 2) {
          state = AUTO_UP;
        } else if (mMoveLen > -mViewHeight / 2) {
          state = AUTO_DOWN;
        }
      } else {
        // 抬起手指时速度方向决定两个View往哪移动
        if (mYV < 0)
          state = AUTO_UP;
        else
          state = AUTO_DOWN;
      }
      mTimer.schedule(2);
      try {
        vt.recycle();
      } catch (Exception e) {
        e.printStackTrace();
      }
      break; 

    }
    super.dispatchTouchEvent(ev);
    return true;
  } 

  @Override
  protected void onLayout(boolean changed, int l, int t, int r, int b) {
    topView.layout(0, (int) mMoveLen, mViewWidth,
        topView.getMeasuredHeight() + (int) mMoveLen);
    bottomView.layout(0, topView.getMeasuredHeight() + (int) mMoveLen,
        mViewWidth, topView.getMeasuredHeight() + (int) mMoveLen
            + bottomView.getMeasuredHeight());
  } 

  @Override
  protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
    super.onMeasure(widthMeasureSpec, heightMeasureSpec);
    if (!isMeasured) {
      isMeasured = true; 

      mViewHeight = getMeasuredHeight();
      mViewWidth = getMeasuredWidth(); 

      topView = getChildAt(0);
      bottomView = getChildAt(1); 

      bottomView.setOnTouchListener(bottomViewTouchListener);
      topView.setOnTouchListener(topViewTouchListener);
    }
  } 

  private OnTouchListener topViewTouchListener = new OnTouchListener() { 

    @Override
    public boolean onTouch(View v, MotionEvent event) {
      ScrollView sv = (ScrollView) v;
      if (sv.getScrollY() == (sv.getChildAt(0).getMeasuredHeight() - sv
          .getMeasuredHeight()) && mCurrentViewIndex == 0)
        canPullUp = true;
      else
        canPullUp = false;
      return false;
    }
  };
  private OnTouchListener bottomViewTouchListener = new OnTouchListener() { 

    @Override
    public boolean onTouch(View v, MotionEvent event) {
      ScrollView sv = (ScrollView) v;
      if (sv.getScrollY() == 0 && mCurrentViewIndex == 1)
        canPullDown = true;
      else
        canPullDown = false;
      return false;
    }
  }; 

  class MyTimer {
    private Handler handler;
    private Timer timer;
    private MyTask mTask; 

    public MyTimer(Handler handler) {
      this.handler = handler;
      timer = new Timer();
    } 

    public void schedule(long period) {
      if (mTask != null) {
        mTask.cancel();
        mTask = null;
      }
      mTask = new MyTask(handler);
      timer.schedule(mTask, 0, period);
    } 

    public void cancel() {
      if (mTask != null) {
        mTask.cancel();
        mTask = null;
      }
    } 

    class MyTask extends TimerTask {
      private Handler handler; 

      public MyTask(Handler handler) {
        this.handler = handler;
      } 

      @Override
      public void run() {
        handler.obtainMessage().sendToTarget();
      } 

    }
  } 

}

注释写的很清楚了,有几个关键点需要讲一下
    1、由于这里为两个ScrollView设置了OnTouchListener,所以在其他地方不能再设置了,否则就白搭了。

2、两个ScrollView的layout参数统一由mMoveLen决定。

3、变量mEvents有两个作用:一是防止手动滑到底部或顶部时继续滑动而改变布局,必须再次按下才能继续滑动;二是在新的pointer down或up时把mEvents设置成-1可以舍弃将要到来的第一个move事件,防止mMoveLen出现剧变。为什么会出现剧变呢?因为假设一开始只有一只手指在滑动,记录的坐标值是这个pointer的事件坐标点,这时候另一只手指按下了导致事件又多了一个pointer,这时候到来的move事件的坐标可能就变成了新的pointer的坐标,这时计算与上一次坐标的差值就会出现剧变,变化的距离就是两个pointer间的距离。所以要把这个move事件舍弃掉,让mLastY值记录这个pointer的坐标再开始计算mMoveLen。pointer up的时候也一样。

理解了这几点,看起来就没什么难度了,代码量也很小。

MainActivity的布局:

<RelativeLayout xmlns:android="http://schemas.android.com/apk/res/android"
  android:layout_width="match_parent"
  android:layout_height="match_parent" > 

  <com.jingchen.tbviewer.ScrollViewContainer
    android:layout_width="match_parent"
    android:layout_height="match_parent" > 

    <ScrollView
      android:layout_width="match_parent"
      android:layout_height="match_parent" > 

      <RelativeLayout
        android:layout_width="wrap_content"
        android:layout_height="wrap_content" > 

        <LinearLayout
          android:id="@+id/imagesLayout"
          android:layout_width="match_parent"
          android:layout_height="wrap_content"
          android:gravity="center_horizontal"
          android:orientation="vertical" > 

          <ImageView
            android:layout_width="wrap_content"
            android:layout_height="wrap_content"
            android:background="@drawable/h" /> 

          <ImageView
            android:layout_width="wrap_content"
            android:layout_height="wrap_content"
            android:background="@drawable/i" /> 

          <ImageView
            android:layout_width="wrap_content"
            android:layout_height="wrap_content"
            android:background="@drawable/j" /> 

          <ImageView
            android:layout_width="wrap_content"
            android:layout_height="wrap_content"
            android:background="@drawable/k" /> 

          <ImageView
            android:layout_width="wrap_content"
            android:layout_height="wrap_content"
            android:background="@drawable/l" /> 

          <ImageView
            android:layout_width="wrap_content"
            android:layout_height="wrap_content"
            android:background="@drawable/m" />
        </LinearLayout> 

        <TextView
          android:layout_width="match_parent"
          android:layout_height="60dp"
          android:layout_below="@id/imagesLayout"
          android:background="#eeeeee"
          android:gravity="center"
          android:text="继续拖动,查看更多美女"
          android:textSize="20sp" />
      </RelativeLayout>
    </ScrollView> 

    <ScrollView
      android:layout_width="match_parent"
      android:layout_height="match_parent"
      android:background="#000000" > 

      <LinearLayout
        android:layout_width="match_parent"
        android:layout_height="match_parent"
        android:gravity="center_horizontal"
        android:orientation="vertical" > 

        <ImageView
          android:layout_width="wrap_content"
          android:layout_height="wrap_content"
          android:background="@drawable/a" /> 

        <ImageView
          android:layout_width="wrap_content"
          android:layout_height="wrap_content"
          android:background="@drawable/b" /> 

        <ImageView
          android:layout_width="wrap_content"
          android:layout_height="wrap_content"
          android:background="@drawable/c" /> 

        <ImageView
          android:layout_width="wrap_content"
          android:layout_height="wrap_content"
          android:background="@drawable/d" /> 

        <ImageView
          android:layout_width="wrap_content"
          android:layout_height="wrap_content"
          android:background="@drawable/e" /> 

        <ImageView
          android:layout_width="wrap_content"
          android:layout_height="wrap_content"
          android:background="@drawable/f" /> 

        <ImageView
          android:layout_width="wrap_content"
          android:layout_height="wrap_content"
          android:background="@drawable/g" />
      </LinearLayout>
    </ScrollView>
  </com.jingchen.tbviewer.ScrollViewContainer> 

</RelativeLayout>

在ScrollView中放了几张图片而已。
MainActivity的代码:

package com.jingchen.tbviewer; 

import android.app.Activity;
import android.os.Bundle;
import android.view.Menu; 

public class MainActivity extends Activity
{
  @Override
  protected void onCreate(Bundle savedInstanceState)
  {
    super.onCreate(savedInstanceState);
    setContentView(R.layout.activity_main);
  } 

  @Override
  public boolean onCreateOptionsMenu(Menu menu)
  {
    getMenuInflater().inflate(R.menu.main, menu);
    return true;
  } 

} 

以上就是本文的全部内容,希望对大家的学习有所帮助。

时间: 2016-03-07

安卓(android)仿电商app商品详情页按钮浮动效果

1.效果图如下: 这效果用户体验还是很酷炫,今天我们就来讲解如何实现这个效果. 2.分析 为了方便理解,作图分析 如图所示,整个页面分为四个部分: 1.悬浮内容,floatView 2.顶部内容,headView 3.中间内容,与悬浮内容相同,middleView 4.商品详情展示页面,detailView 因为页面内容高度会超出屏幕,所以用Scrollview实现滚动,悬浮view与scrollview同级,都在一个帧布局或者相对布局中. 当y方向的滚动距离小于中间的内容middleView到

Android仿京东、天猫商品详情页

前言 前面在介绍控件TabLayout控件和CoordinatorLayout使用的时候说了下实现京东.天猫详情页面的效果,今天要说的是优化版,是我们线上实现的效果,首先看一张效果: 项目结构分析 首先我们来分析一下要实现上面的效果,我们需要怎么做.顶部是一个可以滑动切换Tab,可以用ViewPager+Fragment实现,也可以使用系统的TabLayout控件实现:而下面的 View是一个可以滑动拖动效果的View,可以采用网上一个叫做DragLayout的控件,我这里是自己实现了一个,主要

Android实现商品展示效果

一. 创建手机界面布局 创建一个activity_main.xml文件代码如下: <?xml version="1.0" encoding="utf-8"?> <LinearLayout xmlns:android="http://schemas.android.com/apk/res/android" xmlns:tools="http://schemas.android.com/tools" androi

Android 仿京东、拼多多商品分类页的示例代码

最近接了一个项目,要仿照京东写一个商品分类页,但需要滑动右边子分类,左边的主分类也跟着变换,写了个demo,需要的同学可以自取. 先放一个写完之后的样子: 写这个需求的思路也很清晰,首先左边肯定是一个listView,右边也是一个listView,这两个listView要达到一个联动的效果.右边的listView再嵌套一个GridView即可.如下图所示. 所以,我们需要的数据结构也就确定了,应该是数组套数组,也就说护肤大分类下又有子分类商品,类似于这个样子: ok,数据和UI结构确定了,就可以

Android 仿淘宝商品属性标签页

需求 1.动态加载属性,如尺码,颜色,款式等 由于每件商品的属性是不确定的,有的商品的属性是颜色和尺码,有的是口味,有的是大小,所以这些属性不能直接写死到页面上. 2.动态加载属性下的标签 每个属性下的标签个数也不是一定的,比如有的商品的尺码是是S,M,XL,有的是均码,也就是每种属性的具体的内容是不一定的. 技术点 自定义ViewGroup,使其中的TextView可以依据内容长短自动换行,如下图所示 实现 布局 通过ListView来显示商品所有属性,每种属性作为ListView的Item.

Android仿淘宝切换商品列表布局效果的示例代码

最近电商项目中有这样一个需求,就是在进入商品列表界面,有一个按钮可以切换商品列表的布局(网格或者垂直列表排列). 效果图: 上面两幅图分别是点击右上角按钮后显示两种不同布局的效果.简单的流程可以概括为:第一次进入页面,有个默认的布局(网格布局),点击按钮,由网格布局切换到竖直的线性布局,再次点击切换到网格布局. 分析: 可以看到商品展示的形式都是以列表的方式来展现,我用的是RecyclerView,这种列表并不复杂,配合Adapter数据适配器就实现了. 提出这个需求时,问了朋友,他说使用了两个

CSS仿淘宝首页导航条布局效果

以下是CSS内容部分: /*子鼠*/ body{ font-size:12px; text-align:center; margin-top:30px; font-family:Verdana;} div,img{margin:0; padding:0; border:0;} ul,li{list-style-type: none; margin:0; padding:0; float:left; } #info{ margin-left:auto; margin-right:auto;widt

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

一.淘宝商品详情页效果 我们的效果 二.实现思路 使用两个scrollView,两个scrollView 竖直排列,通过自定义viewGroup来控制两个scrollView的竖直排列,以及滑动事件的处理.如下图 三.具体实现 1.继承viewGroup自定义布局View 重写onMeasure()和onLayout方法,在onLayout方法中完成对两个子ScrollView的竖直排列布局,代码如下: 布局文件: <RelativeLayout xmlns:android="http:/

Android仿淘宝商品详情页效果

本文实例为大家分享了Android仿淘宝商品详情页的具体代码,供大家参考,具体内容如下 Demo地址:先上效果图 效果就是上面图片的效果 接下来看看如何实现 首先我们来看下布局文件 <LinearLayout android:id="@+id/header" android:layout_width="match_parent" android:layout_height="72dp" android:paddingTop="24

Android仿淘宝详情页面viewPager滑动到最后一张图片跳转的功能

需要做一个仿淘宝客户端ViewPager滑动到最后一页,再拖动的时候跳到详情的功能,刚开始没什么思路,后来搜了一下,发现有好几种实现方法,最好的一种就是在ViewPager图片的后面再加一个view,然后滑动viewpager的时候,判断一下就行了. 附一个链接,我写的代码就是参考的这个,稍微改了一点点,先看看效果图. 实现起来比较简单,先写一个滑动加载详情的布局,然后在viewpager的instantiateItem里面判断一下,如果是最后一张,就显示加载详情的那个布局.不过需要注意的是,v

Android实现淘宝选中商品尺寸的按钮组实例

话不多说,先上个效果图: 现在我们就来说说里面的一些原理把! 一.原理: 1.其实这里我们用到的是一个ViewGroup控件组,把这些按钮加进去就有这种效果了!不过这里要继承ViewGroup(命名为:GoodsViewGroup)重写里面的一些方法. 2.主要的方法有: GoodsViewGroup按钮组的控件大小 protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) 里面的按钮每个的位置坐标 protect

Android仿淘宝view滑动至屏幕顶部会一直停留在顶部的位置

在刚刚完成的项目中,在一个页面中,用户体验师提出引用户操作的入住按钮要一直保留在页面当中,不管页面能滚动多长都得停留在页面的可视区域.最终实现效果如下图所示:   如图中的红色框中的view始终会停留在页面中,如果滑动至页面的顶部,会一直保留在顶部. 下面来说下具体的实现思路: 思路:其实整个页面当中一共有两个视觉效果一样的View,通过滑动的位置来进行View的隐藏和显示来达到这种效果.整个页面的在上下滑动的过程中可以总结为两个状态,状态A(如图1所示),view2在可视区域内时,view1不

Android仿淘宝头条基于TextView实现上下滚动通知效果

最近有个项目需要实现通知栏的上下滚动效果,仿淘宝头条的那种. 我从网上看了一些代码,把完整的效果做了出来.如图所示: 具体代码片段如下: 1.在res文件夹下新建anmin文件夹,在这个文件夹里创建两个文件 (1).anim_marquee_in.xml进入时动画 <?xml version="1.0" encoding="utf-8"?> <set xmlns:android="http://schemas.android.com/ap

原生JS实现仿淘宝网左侧商品分类菜单效果代码

本文实例讲述了原生JS实现仿淘宝网左侧商品分类菜单效果代码.分享给大家供大家参考.具体如下: 这是一款原生JS实现的仿淘宝网左侧商品分类菜单效果代码,JavaScript技术实现,兼容各主流浏览器.自己再修改一下CSS菜单,它会变得更漂亮. 运行效果截图如下: 在线演示地址如下: http://demo.jb51.net/js/2015/js-f-taobao-pro-menu-style-codes/ 具体代码如下: <!DOCTYPE html> <head> <titl