近期公司在新版本号上有一个须要。 要在首页加入一个滑动效果, 详细就是仿照X宝的商品详情页, 拉到页面底部时有一个粘滞效果,

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

watermark/2/text/aHR0cDovL2Jsb2cuY3Nkbi5uZXQv/font/5a6L5L2T/fontsize/400/fill/I0JBQkFCMA==/dissolve/70/gravity/Center" alt="">

刚開始是想拿来主义。直接从网上找个现成的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() {
// 设置页面id
mPageTop.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());
*/
@Override
public 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()。
*/
@Override
public 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 touch
mLastMotionY = 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 drag
mTouchState = 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事件
*/
@Override
public 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 event
final 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须要返回当前页是否已经在底部

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

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

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

自己定义ViewGroup实现仿淘宝的商品详情页的更多相关文章

  1. iOS 集成阿里百川最新版(3.1.1.96) 实现淘宝授权登录以及调用淘宝客户端商品详情页

      公司最近要做第三方登录,由于是做导购项目,必不可少的有淘宝的授权登录.本来就是一个授权登录,没什么大不了的.但淘宝的无线开放业务——阿里百川更新的最新版本3.1.1.96,开发文档不是不详细,是很 ...

  2. 使用Python 爬取 京东 ,淘宝。 商品详情页的数据。(避开了反爬虫机制)

    以下是爬取京东商品详情的Python3代码,以excel存放链接的方式批量爬取.excel如下 代码如下 from selenium import webdriver from lxml import ...

  3. iOS开发 仿淘宝,京东商品详情3D动画

    - (void)show { [[UIApplication sharedApplication].windows[0] addSubview:self.projectView]; CGRect fr ...

  4. Android 仿电商app商品详情页按钮浮动效果

    1.效果图如下: 这效果用户体验还是很酷炫,今天我们就来讲解如何实现这个效果. 2.分析 为了方便理解,作图分析 如图所示,整个页面分为四个部分: 1.悬浮内容,floatView 2.顶部内容,he ...

  5. App 仿淘宝:控制详情和购买须知样式切换,控制商品详情和购买须知选项卡的位置(固定在顶部还是正常)

    CSS: <div id="details" ref="details" class="details" :class="t ...

  6. Android点击跳转到淘宝的某一商品详情页或者某一店铺页面

    最近项目的有个需求是点击购买资料按钮进入淘宝界面,简单分析一下,如果用户手机有淘宝就打开淘宝的页面,没有的话也可以选择使用webView进行展示,还是使用手机浏览器进行展示. 判断有无淘宝的代码就不贴 ...

  7. Android自己定义控件实战——仿淘宝商品浏览界面

    转载请声明出处http://blog.csdn.net/zhongkejingwang/article/details/38656929 用手机淘宝浏览商品详情时,商品图片是放在后面的,在第一个Scr ...

  8. Android自定义控件实战——仿淘宝商品浏览界面

    转载请声明出处http://blog.csdn.net/zhongkejingwang/article/details/38656929 用手机淘宝浏览商品详情时,商品图片是放在后面的,在第一个Scr ...

  9. Android中仿淘宝首页顶部滚动自定义HorizontalScrollView定时水平自动切换图片

    Android中仿淘宝首页顶部滚动自定义HorizontalScrollView定时水平自动切换图片 自定义ADPager 自定义水平滚动的ScrollView效仿ViewPager 当遇到要在Vie ...

随机推荐

  1. maya shell 和 UV shell 的区别

    maya shell 和 UV shell 的区别 shell 是 maya 模型自身分离的部分 UV shell 是 UV 分离的部分 有多少个shell,就至少有多少个 UV shell,但是一个 ...

  2. tp5的路由

    路由模式:普通.强制和混合 普通模式: //配置文件关闭路由,完全使用默认的PATH_INFO方式URL 'url_route_on' => false, 关闭路由后的普通模式任然可以通过操作方 ...

  3. swap

    添加交换分区 SWAP(交换)分区是一种通过在硬盘中预先划分一定的空间,然后将把内存中暂时不常用的数据临时存放到硬盘中,以便腾出物理内存空间让更活跃的程序服务来使用的技术,其设计目的是为了解决真实物理 ...

  4. XamarinAndroid组件教程RecylerView适配器设置动画示例

    XamarinAndroid组件教程RecylerView适配器设置动画示例 [示例1-3]下面将在RecylerView的子元素进行滚动时,使用适配器动画.具体的操作步骤如下: (1)创建一个名为R ...

  5. js变量和函数声明的提升

    函数声明和变量声明总是会被解释器悄悄地被“提升”到方法体的最顶部 请注意,变量赋值并没有被提升,只是声明被提升了. 函数的声明比变量的声明具有高的优先级. 下面的程序是什么结果? var foo =  ...

  6. Redis自学笔记:4.1进阶-事务

    第4章:进阶 4.1事务 4.1.1概述 redis中的事务是一组命令的集合 事务同命令一样都是redis的最小执行单位,一个事务中的命令要么都执行, 要么都不执行 事务的原理是先将一个事务的命令发送 ...

  7. LOJ.6235.区间素数个数(Min_25筛)

    题目链接 \(Description\) 给定\(n\),求\(1\sim n\)中的素数个数. \(2\leq n\leq10^{11}\). \(Solution\) Min_25筛.只需要求出\ ...

  8. Python3之弹性力学——应力张量1

    题目 已知某点的应力张量为: \[ \left[ \begin{array}{ccc} \sigma_{x} &\tau_{xy} &\tau_{xz}\\ \tau_{yx} &am ...

  9. 2545 ACM 博客 比较树的路径长短

    题目:http://acm.hdu.edu.cn/showproblem.php?pid=2545 题意:比较树的路径长短 思路:利用数组存入父节点的值, 例如: 5 2 1 2 1 3 3 4 3 ...

  10. python3 词法拆分

    1.可以利用translate+string模块 2.可以利用jieba进行分词(结巴分词会分成词,但是我需要断句,所以这里不用) 3.利用python内置函数解决 仅仅只列出第3种方法,其他两种方法 ...