自定义ViewGroup实现仿淘宝的商品详情页

最近公司在新版本上有一个需要, 要在首页添加一个滑动效果, 具体就是仿照X宝的商品详情页, 拉到页面底部时有一个粘滞效果,

 如下图 X东的商品详情页,如果用户继续向上拉的话就进入商品图文描述界面:



刚开始是想拿来主义,直接从网上找个现成的demo来用, 但是网上无一例外的答案都特别统一: 几乎全部是ScrollView中再套两个ScrollView,或者是一个LinearLayout中套两个ScrollView。 通过指定父view和子view的focus来切换滑动的处理界面---即通过view的requestDisallowInterceptTouchEvent方法来决定是哪一个ScrollView来处理滑动事件。

使用以上方法虽然可以解一时之渴, 但是存在几点缺陷:

1  扩展性不强 : 如果后续产品要求不止是两页滑动呢,是三页滑动呢, 难道要嵌3个ScrollView并通过N个判断来实现吗

2  兼容性不强 : 如果需要在某一个子页中需要处理左右滑动事件或者双指操作事件呢, 此方法就无法实现了

3 个人原因 : 个人喜欢自己掌握主动性,事件的处理自己来控制更靠谱一些(PS:就如同一份感情一样,需要细心去经营^_^)


总和以上原因, 自己实现了一个ViewGroup,实现文章开头提到的效果, 废话不多说  直接上源码,以下只是部分主要源码,并对每一个方法都做了注释,可以参照注释理解。   文章最后对这个ViewGroup加了一点实现的细节以及如何使用此VIewGroup, 以及demo地址

package com.mcoy.snapscrollview;import android.content.Context;
import android.util.AttributeSet;
import android.util.Log;
import android.view.MotionEvent;
import android.view.VelocityTracker;
import android.view.View;
import android.view.ViewConfiguration;
import android.view.ViewGroup;
import android.widget.Scroller;/*** @author jiangxinxing---mcoy in English* * 了解此ViewGroup之前, 有两点一定要做到心中有数* 一个是对Scroller的使用, 另一个是对onInterceptTouchEvent和onTouchEvent要做到很熟悉* 以下几个网站可以做参考用* http://blog.csdn.net/bigconvience/article/details/26697645* http://blog.csdn.net/androiddevelop/article/details/8373782* http://blog.csdn.net/xujainxing/article/details/8985063*/
public class McoySnapPageLayout extends ViewGroup {。。。。public interface McoySnapPage {/*** 返回page根节点* * @return*/View getRootView();/*** 是否滑动到最顶端* 第二页必须自己实现此方法,来判断是否已经滑动到第二页的顶部* 并决定是否要继续滑动到第一页*/boolean isAtTop();/*** 是否滑动到最底部* 第一页必须自己实现此方法,来判断是否已经滑动到第二页的底部* 并决定是否要继续滑动到第二页*/boolean isAtBottom();}public interface PageSnapedListener {/*** @mcoy* 当从某一页滑动到另一页完成时的回调函数*/void onSnapedCompleted(int derection);}。。。。。。/*** 设置上下页面* @param pageTop* @param pageBottom*/public void setSnapPages(McoySnapPage pageTop, McoySnapPage pageBottom) {mPageTop = pageTop;mPageBottom = pageBottom;addPagesAndRefresh();}private void addPagesAndRefresh() {// 设置页面idmPageTop.getRootView().setId(0);mPageBottom.getRootView().setId(1);addView(mPageTop.getRootView());addView(mPageBottom.getRootView());postInvalidate();}/*** @mcoy add* computeScroll方法会调用postInvalidate()方法, 而postInvalidate()方法中系统* 又会调用computeScroll方法, 因此会一直在循环互相调用, 循环的终结点是在computeScrollOffset()* 当computeScrollOffset这个方法返回false时,说明已经结束滚动。* * 重要:真正的实现此view的滚动是调用scrollTo(mScroller.getCurrX(), mScroller.getCurrY());*/@Overridepublic void computeScroll() {//先判断mScroller滚动是否完成if (mScroller.computeScrollOffset()) {if (mScroller.getCurrY() == (mScroller.getFinalY())) {if (mNextDataIndex > mDataIndex) {mFlipDrection = FLIP_DIRECTION_DOWN;makePageToNext(mNextDataIndex);} else if (mNextDataIndex < mDataIndex) {mFlipDrection = FLIP_DIRECTION_UP;makePageToPrev(mNextDataIndex);}else{mFlipDrection = FLIP_DIRECTION_CUR;}if(mPageSnapedListener != null){mPageSnapedListener.onSnapedCompleted(mFlipDrection);}}//这里调用View的scrollTo()完成实际的滚动scrollTo(mScroller.getCurrX(), mScroller.getCurrY());//必须调用该方法,否则不一定能看到滚动效果postInvalidate();}}private void makePageToNext(int dataIndex) {mDataIndex = dataIndex;mCurrentScreen = getCurrentScreen();}private void makePageToPrev(int dataIndex) {mDataIndex = dataIndex;mCurrentScreen = getCurrentScreen();}public int getCurrentScreen() {for (int i = 0; i < getChildCount(); i++) {if (getChildAt(i).getId() == mDataIndex) {return i;}}return mCurrentScreen;}public View getCurrentView() {for (int i = 0; i < getChildCount(); i++) {if (getChildAt(i).getId() == mDataIndex) {return getChildAt(i);}}return null;}/** (non-Javadoc)* * @see* android.view.ViewGroup#onInterceptTouchEvent(android.view.MotionEvent)* 重写了父类的onInterceptTouchEvent(),主要功能是在onTouchEvent()方法之前处理* touch事件。包括:down、up、move事件。* 当onInterceptTouchEvent()返回true时进入onTouchEvent()。*/@Overridepublic boolean onInterceptTouchEvent(MotionEvent ev) {final int action = ev.getAction();if ((action == MotionEvent.ACTION_MOVE)&& (mTouchState != TOUCH_STATE_REST)) {return true;}final float x = ev.getX();final float y = ev.getY();switch (action) {case MotionEvent.ACTION_MOVE:// 记录y与mLastMotionY差值的绝对值。// yDiff大于gapBetweenTopAndBottom时就认为界面拖动了足够大的距离,屏幕就可以移动了。final int yDiff = (int)(y - mLastMotionY);boolean yMoved = Math.abs(yDiff) > gapBetweenTopAndBottom;if (yMoved) {if(MCOY_DEBUG) {Log.e(TAG, "yDiff is " + yDiff);Log.e(TAG, "mPageTop.isFlipToBottom() is " + mPageTop.isAtBottom());Log.e(TAG, "mCurrentScreen is " + mCurrentScreen);Log.e(TAG, "mPageBottom.isFlipToTop() is " + mPageBottom.isAtTop());}if(yDiff < 0 && mPageTop.isAtBottom() && mCurrentScreen == 0 || yDiff > 0 && mPageBottom.isAtTop() && mCurrentScreen == 1){Log.e("mcoy", "121212121212121212121212");mTouchState = TOUCH_STATE_SCROLLING;}}break;case MotionEvent.ACTION_DOWN:// Remember location of down touchmLastMotionY = y;Log.e("mcoy", "mScroller.isFinished() is " + mScroller.isFinished());mTouchState = mScroller.isFinished() ? TOUCH_STATE_REST: TOUCH_STATE_SCROLLING;break;case MotionEvent.ACTION_CANCEL:case MotionEvent.ACTION_UP:// Release the dragmTouchState = TOUCH_STATE_REST;break;}boolean intercept = mTouchState != TOUCH_STATE_REST;Log.e("mcoy", "McoySnapPageLayout---onInterceptTouchEvent return " + intercept);return intercept;}/** (non-Javadoc)* * @see android.view.View#onTouchEvent(android.view.MotionEvent)* 主要功能是处理onInterceptTouchEvent()返回值为true时传递过来的touch事件*/@Overridepublic boolean onTouchEvent(MotionEvent ev) {Log.e("mcoy", "onTouchEvent--" + System.currentTimeMillis());if (mVelocityTracker == null) {mVelocityTracker = VelocityTracker.obtain();}mVelocityTracker.addMovement(ev);final int action = ev.getAction();final float x = ev.getX();final float y = ev.getY();switch (action) {case MotionEvent.ACTION_DOWN:if (!mScroller.isFinished()) {mScroller.abortAnimation();}break;case MotionEvent.ACTION_MOVE:if(mTouchState != TOUCH_STATE_SCROLLING){// 记录y与mLastMotionY差值的绝对值。// yDiff大于gapBetweenTopAndBottom时就认为界面拖动了足够大的距离,屏幕就可以移动了。final int yDiff = (int) Math.abs(y - mLastMotionY);boolean yMoved = yDiff > gapBetweenTopAndBottom;if (yMoved) {mTouchState = TOUCH_STATE_SCROLLING;}}// 手指拖动屏幕的处理if ((mTouchState == TOUCH_STATE_SCROLLING)) {// Scroll to follow the motion eventfinal int deltaY = (int) (mLastMotionY - y);mLastMotionY = y;final int scrollY = getScrollY();if(mCurrentScreen == 0){//显示第一页,只能上拉时使用if(mPageTop != null && mPageTop.isAtBottom()){scrollBy(0, Math.max(-1 * scrollY, deltaY));}}else{if(mPageBottom != null && mPageBottom.isAtTop()){scrollBy(0, deltaY);}}}break;case MotionEvent.ACTION_CANCEL:case MotionEvent.ACTION_UP:// 弹起手指后,切换屏幕的处理if (mTouchState == TOUCH_STATE_SCROLLING) {final VelocityTracker velocityTracker = mVelocityTracker;velocityTracker.computeCurrentVelocity(1000, mMaximumVelocity);int velocityY = (int) velocityTracker.getYVelocity();if (Math.abs(velocityY) > SNAP_VELOCITY) {if( velocityY > 0 && mCurrentScreen == 1 && mPageBottom.isAtTop()){snapToScreen(mDataIndex-1);}else if(velocityY < 0  && mCurrentScreen == 0){snapToScreen(mDataIndex+1);}else{snapToScreen(mDataIndex);}} else {snapToDestination();}if (mVelocityTracker != null) {mVelocityTracker.recycle();mVelocityTracker = null;}}else{}mTouchState = TOUCH_STATE_REST;break;default:break;}return true;}private void clearOnTouchEvents(){mTouchState = TOUCH_STATE_REST;if (mVelocityTracker != null) {mVelocityTracker.recycle();mVelocityTracker = null;}}private void snapToDestination() {// 计算应该去哪个屏final int flipHeight = getHeight() / 8;int whichScreen = -1;final int topEdge = getCurrentView().getTop();if(topEdge < getScrollY() && (getScrollY()-topEdge) >= flipHeight && mCurrentScreen == 0){//向下滑动    whichScreen = mDataIndex + 1;}else if(topEdge > getScrollY() && (topEdge - getScrollY()) >= flipHeight && mCurrentScreen == 1){//向上滑动whichScreen = mDataIndex - 1;}else{whichScreen = mDataIndex;}Log.e(TAG, "snapToDestination mDataIndex = " + mDataIndex);Log.e(TAG, "snapToDestination whichScreen = " + whichScreen);snapToScreen(whichScreen);}private void snapToScreen(int dataIndex) {if (!mScroller.isFinished())return;final int direction = dataIndex - mDataIndex;mNextDataIndex = dataIndex;boolean changingScreens = dataIndex != mDataIndex;View focusedChild = getFocusedChild();if (focusedChild != null && changingScreens) {focusedChild.clearFocus();}//在这里判断是否已到目标位置~int newY = 0;switch (direction) {case 1:  //需要滑动到第二页Log.e(TAG, "the direction is 1");newY = getCurrentView().getBottom(); // 最终停留的位置break;case -1:  //需要滑动到第一页Log.e(TAG, "the direction is -1");Log.e(TAG, "getCurrentView().getTop() is "+ getCurrentView().getTop() + " getHeight() is "+ getHeight());newY = getCurrentView().getTop() - getHeight(); // 最终停留的位置break;case 0:  //滑动距离不够, 因此不造成换页,回到滑动之前的位置Log.e(TAG, "the direction is 0");newY = getCurrentView().getTop(); //第一页的top是0, 第二页的top应该是第一页的高度break;default:break;}final int cy = getScrollY(); // 启动的位置Log.e(TAG, "the newY is " + newY + " cy is " + cy);final int delta = newY - cy; // 滑动的距离,正值是往左滑<—,负值是往右滑—>mScroller.startScroll(0, cy, 0, delta, Math.abs(delta));invalidate();}。。。。}

McoySnapPage是定义在VIewGroup的一个接口, 比如说我们需要类似某东商品详情那样,有上下两页的效果。 那我就需要自己定义两个类实现这个接口,并实现接口的方法。getRootView需要返回当前页需要显示的布局内容;isAtTop需要返回当前页是否已经在顶端; isAtBottom需要返回当前页是否已经在底部

onInterceptTouchEventonTouchEvent决定当前的滑动状态, 并决定是有当前VIewGroup拦截touch事件还是由子view去消费touch事件


Demo地址: http://download.csdn.net/detail/zxm317122667/8926295

PS: Mcoy是本人的英文名称, 希望不要引起误会^_^


本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.rhkb.cn/news/34198.html

如若内容造成侵权/违法违规/事实不符,请联系长河编程网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

实战淘宝穿衣搭配

1. 说明 《淘宝穿衣搭配》比赛是2015年的一个天池算法比赛&#xff0c;现已开放为新人赛&#xff0c;仍可下载数据&#xff0c;上传结果及计算排名。具体地址是&#xff1a; https://tianchi.aliyun.com/getStart/information.htm?spm5176.100067.5678.2.78904065HrZLpP&…

【Android】仿淘宝商品详情页

话不多说---先上效果图&#xff1a; 图1 商品详情页 图2 加入购物车点击事件 需求分析&#xff1a; 图1中主要需要实现的效果&#xff1a; 1.轮播图 2.顶部导航栏的渐变 3.顶部导航栏随着滑动的位置选择对应的值以及点击滑动到对应位置 图2中主要需要实现的效果&#xff1…

仿淘宝— 商品图片切换

在线展示 html: <div id"box"><ul><li id"li01"><img src"./images/01.jpg" alt""></li><li id"li02"><img src"./images/02.jpg" alt""></li><l…

淘宝内容场下的人物理解系统

本文的人物理解特指对人的视觉特征的识别&#xff0c;即从视觉维度&#xff0c;获取视频或图片中人物的身份、性别、年龄、颜值、身材、服饰、人-物关系等各种信息。 背景介绍 随着网络通信技术的迅猛发展&#xff0c;主流的信息传播已经揉合了文本、图像、语音、视频等多种媒体…

实战3-淘宝用户行为分析及可视化

淘宝用户行为分析及可视化 目录 淘宝用户行为分析及可视化分析背景明确问题读取和理解数据数据预处理数据分析与可视化用户行为分析日PV和日UVPV与UV相关性可视化 时PV和时UV相关性可视化 不同行为类型用户PV分析操作行为分析操作行为情况操作行为可视化 用户消费行为分析日ARP…

怎样一同下载淘宝天猫的商品主图详情图细节图和属性图并保存

我们都知道一个商品链接里的图片素材&#xff0c;包含有主图、主图视频、详情图、属性图等等&#xff0c;那么我们能不能把一整个链接里的所有商品图一次都给导出来保存呢&#xff1f;答案是&#xff0c;当然是可以的&#xff0c;可我们该怎么正确的去操作呢&#xff1f;下面小…

超稳定的接口——淘宝/天猫获得淘宝商品详情

item_get-获得淘宝商品详情 注册开通 onebound.taobao.item_get 公共参数 名称类型必须描述keyString是调用key&#xff08;必须以GET方式拼接在URL中&#xff09;secretString是调用密钥api_nameString是API接口名称&#xff08;包括在请求地址中&#xff09;[item_search,…

弘辽科技:如何拍摄出效果好的淘宝主图?商品主图的基本要求有?

原标题《弘辽科技&#xff1a;如何拍摄出效果好的淘宝主图&#xff1f;商品主图的基本要求有&#xff1f;》 开店后&#xff0c;对于商家而言&#xff0c;接下来就是对店铺的一系列的运营和优化操作。比如商品主图设计与拍摄、店铺装修、商品发布、订单管理和配送等工作。可以…

淘宝用户行为分析

一、项目介绍 1.概述 本数据集是阿里巴巴提供的一个淘宝用户行为数据集&#xff08;数据来源&#xff1a;数据集-阿里云天池&#xff09;&#xff0c;包含了2017年11月25日至2017年12月3日之间有行为的约一百万随机用户的所有行为&#xff08;行为包括点击、购买、加购、喜欢&…

模仿淘宝主页

图片资源链接&#xff1a;http://pan.baidu.com/s/1jHAdLNg 密码&#xff1a;5uo1 代码资源链接&#xff1a;链接&#xff1a;http://pan.baidu.com/s/1slRaUIT 密码&#xff1a;8kin html 代码如下&#xff1a; <!DOCTYPE html> <html lang"en"> …

Android模仿淘宝详情页界面

话不多说—先上效果图&#xff1a; 图1中主要需要实现的效果&#xff1a; 1.轮播图 2.顶部导航栏的渐变 3.顶部导航栏随着滑动的位置选择对应的值以及点击滑动到对应位置 Android模仿淘宝详情页界面文件&#xff1a;url80.ctfile.com/f/25127180-734377737-78795d?p551685…

聊聊淘宝天猫个性化推荐技术演进史

引言&#xff1a;个性化推荐技术直面用户&#xff0c;可以说是站在最前线的那个。如今&#xff0c;从用户打开手机淘宝客户端&#xff08;简称“手淘”&#xff09;或是手机天猫客户端&#xff08;简称“猫客”&#xff09;的那一刻起&#xff0c;个性化推荐技术就已经启动&…

淘宝买家秀后台操作与各场景展示逻辑

宝贝洋淘买家秀&#xff1a;可操作加精展示在宝贝评价页面 店铺洋淘买家秀&#xff1a;店铺买家秀内容查看及管理 精选买家秀&#xff1a;被平台审核通过的买家秀&#xff0c;就是精选买家秀集合地。可操作转发至微淘。 加精后的买家秀会在详情页下出现&#xff0c;效果如下 …

生成式 AI 讲故事,儿童也能创作自己的睡前故事? #Create With Alexa

哈佛大学的 Sandra Cortesi 认为&#xff0c;父母应该尽量多地让孩子接触能够激发其主动创造力的技术。 亚马逊正在通过一项新的 Alexa 功能进入生成式人工智能热潮&#xff0c; 开始涉足讲故事领域&#xff0c;Create With Alexa 就是亚马逊在讲故事技术的一次尝试&#xff0c…

COMSOL空气反应 模型框架

Comsol等离子体模块&#xff0c;40多种空气反应框架&#xff0c;主要N2和O2。

chatgpt赋能python:Python中文版转换指南

Python中文版转换指南 Python是一种流行的编程语言&#xff0c;它的简单易学和开发效率高受到了全球众多开发者的青睐&#xff0c;但是很多初学者非常苦恼的是&#xff0c;Python的官网和文档全都是英文版的。在中国大陆的用户中&#xff0c;这一问题尤为突出。 本文将介绍Py…

chatgpt赋能python:Python文本编码转换详解

Python文本编码转换详解 在Python中&#xff0c;文本编码转换是一个非常常见的任务。本文将详细介绍Python中的文本编码转换&#xff0c;并提供一些常见的示例和代码片段。 什么是文本编码转换&#xff1f; 在计算机中&#xff0c;文本是以二进制形式存储的。文本编码是一种…

Oracle帐户被锁了,怎么解锁

当多次输入错误密码时&#xff0c;会被oracle视为恶意连接&#xff0c;账户就会锁定&#xff0c;解锁也很简单&#xff0c;步骤如下 第一步&#xff1a; 在数据库安装的电脑上&#xff0c;运行窗口中输入cmd&#xff0c;调出命令提示符界面。 第二步&#xff1a; 用管理员身…

Oracle scott账户被锁定,scott默认密码,sys,system默认密码

Windows安装oracle 安装oracle 出现Environment variable: "PATH" 在stage\cvu\目录下修改cvu_prereq.xml文件&#xff0c;在<CERTIFIED_SYSTEMS>字段下添加&#xff1a; 改 cvu_prereq.xml 里面 的配置&#xff0c;cvu_prereq.xml 文件在oracle安装解压包da…

实现登录密码输入错误次数过多,锁定用户账号,前台可以进行解锁的业务(未完善版本)

提示&#xff1a;文章写完后&#xff0c;目录可以自动生成&#xff0c;如何生成可参考右边的帮助文档 文章目录 前言一、前台实现解锁二、后台逻辑实现二、步骤1.数据库2.代码实现 前言 工作的第一天&#xff0c;就接到了一个登录锁定与解锁的需求&#xff0c;第一反应&#x…