前言

前面在介绍控件TabLayout控件和CoordinatorLayout使用的时候说了下实现京东、天猫详情页面的效果,今天要说的是优化版,是我们线上实现的效果,首先看一下效果:

项目结构分析

首先我们来分析一下要实现上面的效果,我们需要怎么做。顶部是一个可以滑动切换Tab,可以用ViewPager+Fragment实现,也可以使用系统的TabLayout控件实现;而下面的 View是一个可以滑动拖动效果的View,可以采用网上一个叫做DragLayout的控件,我这里是自己实现了一个,主要是通过对View的事件分发的一些处理;然后滑动到下面就是一个图文详情的View(Fragment),本页面包含两个界面:详情页面和参数页面;最后是评价的View(Fragment)。经过上面的分析,我们的界面至少需要4个Fragement,首先来看一下项目结构:

代码讲解

代码比较多,这里只讲解几个核心的方法类。首先我们来看一下我们自己是的这个具有阻尼效果的View,我们知道要实现的效果,我们需要对View的事件做一个全面的实现。这里首先说一下View的事件分发的流程:onInterceptTouchEvent()–>dispatchTouchEvent()–>onTouchEvent();
首先我们需要对View传过来的事件做一个拦截:

ensureTarget();if (null == mTarget) {return false;}if (!isEnabled()) {return false;}final int aciton = MotionEventCompat.getActionMasked(ev);boolean shouldIntercept = false;switch (aciton) {case MotionEvent.ACTION_DOWN: {mInitMotionX = ev.getX();mInitMotionY = ev.getY();shouldIntercept = false;break;}case MotionEvent.ACTION_MOVE: {final float x = ev.getX();final float y = ev.getY();final float xDiff = x - mInitMotionX;final float yDiff = y - mInitMotionY;if (canChildScrollVertically((int) yDiff)) {shouldIntercept = false;} else {final float xDiffabs = Math.abs(xDiff);final float yDiffabs = Math.abs(yDiff);if (yDiffabs > mTouchSlop && yDiffabs >= xDiffabs&& !(mStatus == Status.CLOSE && yDiff > 0|| mStatus == Status.OPEN && yDiff < 0)) {shouldIntercept = true;}}break;}case MotionEvent.ACTION_UP:case MotionEvent.ACTION_CANCEL: {shouldIntercept = false;break;}}return shouldIntercept;

最后转发给onTouchEvent

ensureTarget();if (null == mTarget) {return false;}if (!isEnabled()) {return false;}boolean wantTouch = true;final int action = MotionEventCompat.getActionMasked(ev);switch (action) {case MotionEvent.ACTION_DOWN: {if (mTarget instanceof View) {wantTouch = true;}break;}case MotionEvent.ACTION_MOVE: {final float y = ev.getY();final float yDiff = y - mInitMotionY;if (canChildScrollVertically(((int) yDiff))) {wantTouch = false;} else {processTouchEvent(yDiff);wantTouch = true;}break;}case MotionEvent.ACTION_UP:case MotionEvent.ACTION_CANCEL: {finishTouchEvent();wantTouch = false;break;}}return wantTouch;

滑动事件完了之后我们需要调用request方法对View做一个重绘:

final int left = l;final int right = r;int top;int bottom;final int offset = (int) mSlideOffset;View child;for (int i = 0; i < getChildCount(); i++) {child = getChildAt(i);if (child.getVisibility() == GONE) {continue;}if (child == mBehindView) {top = b + offset;bottom = top + b - t;} else {top = t + offset;bottom = b + offset;}child.layout(left, top, right, bottom);}

上下滑动也是涉及到两个界面:mFrontView和mBehindView,然后通过判断滑动事件来显示哪一个View。具体看代码:

package com.xzh.gooddetail.view;import android.animation.Animator;
import android.animation.AnimatorListenerAdapter;
import android.animation.ValueAnimator;
import android.content.Context;
import android.content.res.TypedArray;
import android.os.Parcel;
import android.os.Parcelable;
import android.support.v4.view.MotionEventCompat;
import android.support.v4.view.ViewCompat;
import android.util.AttributeSet;
import android.view.MotionEvent;
import android.view.View;
import android.view.ViewConfiguration;
import android.view.ViewGroup;
import android.widget.AbsListView;
import android.widget.FrameLayout;
import android.widget.LinearLayout;
import android.widget.RelativeLayout;import com.xzh.gooddetail.R;public class SlideDetailsLayout extends ViewGroup {public interface OnSlideDetailsListener {void onStatusChanged(Status status);}public enum Status {CLOSE,OPEN;public static Status valueOf(int stats) {if (0 == stats) {return CLOSE;} else if (1 == stats) {return OPEN;} else {return CLOSE;}}}private static final float DEFAULT_PERCENT = 0.2f;private static final int DEFAULT_DURATION = 300;private View mFrontView;private View mBehindView;private float mTouchSlop;private float mInitMotionY;private float mInitMotionX;private View mTarget;private float mSlideOffset;private Status mStatus = Status.CLOSE;private boolean isFirstShowBehindView = true;private float mPercent = DEFAULT_PERCENT;private long mDuration = DEFAULT_DURATION;private int mDefaultPanel = 0;private OnSlideDetailsListener mOnSlideDetailsListener;public SlideDetailsLayout(Context context) {this(context, null);}public SlideDetailsLayout(Context context, AttributeSet attrs) {this(context, attrs, 0);}public SlideDetailsLayout(Context context, AttributeSet attrs, int defStyleAttr) {super(context, attrs, defStyleAttr);TypedArray a = context.obtainStyledAttributes(attrs, R.styleable.SlideDetailsLayout, defStyleAttr, 0);mPercent = a.getFloat(R.styleable.SlideDetailsLayout_percent, DEFAULT_PERCENT);mDuration = a.getInt(R.styleable.SlideDetailsLayout_duration, DEFAULT_DURATION);mDefaultPanel = a.getInt(R.styleable.SlideDetailsLayout_default_panel, 0);a.recycle();mTouchSlop = ViewConfiguration.get(getContext()).getScaledTouchSlop();}public void setOnSlideDetailsListener(OnSlideDetailsListener listener) {this.mOnSlideDetailsListener = listener;}public void smoothOpen(boolean smooth) {if (mStatus != Status.OPEN) {mStatus = Status.OPEN;final float height = -getMeasuredHeight();animatorSwitch(0, height, true, smooth ? mDuration : 0);}}public void smoothClose(boolean smooth) {if (mStatus != Status.CLOSE) {mStatus = Status.CLOSE;final float height = -getMeasuredHeight();animatorSwitch(height, 0, true, smooth ? mDuration : 0);}}@Overrideprotected LayoutParams generateDefaultLayoutParams() {return new MarginLayoutParams(MarginLayoutParams.WRAP_CONTENT, MarginLayoutParams.WRAP_CONTENT);}@Overridepublic LayoutParams generateLayoutParams(AttributeSet attrs) {return new MarginLayoutParams(getContext(), attrs);}@Overrideprotected LayoutParams generateLayoutParams(LayoutParams p) {return new MarginLayoutParams(p);}@Overrideprotected void onFinishInflate() {final int childCount = getChildCount();if (1 >= childCount) {throw new RuntimeException("SlideDetailsLayout only accept childs more than 1!!");}mFrontView = getChildAt(0);mBehindView = getChildAt(1);if (mDefaultPanel == 1) {post(new Runnable() {@Overridepublic void run() {smoothOpen(false);}});}}@Overrideprotected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {final int pWidth = MeasureSpec.getSize(widthMeasureSpec);final int pHeight = MeasureSpec.getSize(heightMeasureSpec);final int childWidthMeasureSpec =MeasureSpec.makeMeasureSpec(pWidth, MeasureSpec.EXACTLY);final int childHeightMeasureSpec =MeasureSpec.makeMeasureSpec(pHeight, MeasureSpec.EXACTLY);View child;for (int i = 0; i < getChildCount(); i++) {child = getChildAt(i);if (child.getVisibility() == GONE) {continue;}measureChild(child, childWidthMeasureSpec, childHeightMeasureSpec);}setMeasuredDimension(pWidth, pHeight);}@Overrideprotected void onLayout(boolean changed, int l, int t, int r, int b) {final int left = l;final int right = r;int top;int bottom;final int offset = (int) mSlideOffset;View child;for (int i = 0; i < getChildCount(); i++) {child = getChildAt(i);if (child.getVisibility() == GONE) {continue;}if (child == mBehindView) {top = b + offset;bottom = top + b - t;} else {top = t + offset;bottom = b + offset;}child.layout(left, top, right, bottom);}}@Overridepublic boolean onInterceptTouchEvent(MotionEvent ev) {ensureTarget();if (null == mTarget) {return false;}if (!isEnabled()) {return false;}final int aciton = MotionEventCompat.getActionMasked(ev);boolean shouldIntercept = false;switch (aciton) {case MotionEvent.ACTION_DOWN: {mInitMotionX = ev.getX();mInitMotionY = ev.getY();shouldIntercept = false;break;}case MotionEvent.ACTION_MOVE: {final float x = ev.getX();final float y = ev.getY();final float xDiff = x - mInitMotionX;final float yDiff = y - mInitMotionY;if (canChildScrollVertically((int) yDiff)) {shouldIntercept = false;} else {final float xDiffabs = Math.abs(xDiff);final float yDiffabs = Math.abs(yDiff);if (yDiffabs > mTouchSlop && yDiffabs >= xDiffabs&& !(mStatus == Status.CLOSE && yDiff > 0|| mStatus == Status.OPEN && yDiff < 0)) {shouldIntercept = true;}}break;}case MotionEvent.ACTION_UP:case MotionEvent.ACTION_CANCEL: {shouldIntercept = false;break;}}return shouldIntercept;}@Overridepublic boolean onTouchEvent(MotionEvent ev) {ensureTarget();if (null == mTarget) {return false;}if (!isEnabled()) {return false;}boolean wantTouch = true;final int action = MotionEventCompat.getActionMasked(ev);switch (action) {case MotionEvent.ACTION_DOWN: {if (mTarget instanceof View) {wantTouch = true;}break;}case MotionEvent.ACTION_MOVE: {final float y = ev.getY();final float yDiff = y - mInitMotionY;if (canChildScrollVertically(((int) yDiff))) {wantTouch = false;} else {processTouchEvent(yDiff);wantTouch = true;}break;}case MotionEvent.ACTION_UP:case MotionEvent.ACTION_CANCEL: {finishTouchEvent();wantTouch = false;break;}}return wantTouch;}private void processTouchEvent(final float offset) {if (Math.abs(offset) < mTouchSlop) {return;}final float oldOffset = mSlideOffset;if (mStatus == Status.CLOSE) {// reset if pull downif (offset >= 0) {mSlideOffset = 0;} else {mSlideOffset = offset;}if (mSlideOffset == oldOffset) {return;}} else if (mStatus == Status.OPEN) {final float pHeight = -getMeasuredHeight();if (offset <= 0) {mSlideOffset = pHeight;} else {final float newOffset = pHeight + offset;mSlideOffset = newOffset;}if (mSlideOffset == oldOffset) {return;}}requestLayout();}private void finishTouchEvent() {final int pHeight = getMeasuredHeight();final int percent = (int) (pHeight * mPercent);final float offset = mSlideOffset;boolean changed = false;if (Status.CLOSE == mStatus) {if (offset <= -percent) {mSlideOffset = -pHeight;mStatus = Status.OPEN;changed = true;} else {mSlideOffset = 0;}} else if (Status.OPEN == mStatus) {if ((offset + pHeight) >= percent) {mSlideOffset = 0;mStatus = Status.CLOSE;changed = true;} else {mSlideOffset = -pHeight;}}animatorSwitch(offset, mSlideOffset, changed);}private void animatorSwitch(final float start, final float end) {animatorSwitch(start, end, true, mDuration);}private void animatorSwitch(final float start, final float end, final long duration) {animatorSwitch(start, end, true, duration);}private void animatorSwitch(final float start, final float end, final boolean changed) {animatorSwitch(start, end, changed, mDuration);}private void animatorSwitch(final float start,final float end,final boolean changed,final long duration) {ValueAnimator animator = ValueAnimator.ofFloat(start, end);animator.addUpdateListener(new ValueAnimator.AnimatorUpdateListener() {@Overridepublic void onAnimationUpdate(ValueAnimator animation) {mSlideOffset = (float) animation.getAnimatedValue();requestLayout();}});animator.addListener(new AnimatorListenerAdapter() {@Overridepublic void onAnimationEnd(Animator animation) {super.onAnimationEnd(animation);if (changed) {if (mStatus == Status.OPEN) {checkAndFirstOpenPanel();}if (null != mOnSlideDetailsListener) {mOnSlideDetailsListener.onStatusChanged(mStatus);}}}});animator.setDuration(duration);animator.start();}private void checkAndFirstOpenPanel() {if (isFirstShowBehindView) {isFirstShowBehindView = false;mBehindView.setVisibility(VISIBLE);}}private void ensureTarget() {if (mStatus == Status.CLOSE) {mTarget = mFrontView;} else {mTarget = mBehindView;}}protected boolean canChildScrollVertically(int direction) {if (mTarget instanceof AbsListView) {return canListViewSroll((AbsListView) mTarget);} else if (mTarget instanceof FrameLayout ||mTarget instanceof RelativeLayout ||mTarget instanceof LinearLayout) {View child;for (int i = 0; i < ((ViewGroup) mTarget).getChildCount(); i++) {child = ((ViewGroup) mTarget).getChildAt(i);if (child instanceof AbsListView) {return canListViewSroll((AbsListView) child);}}}if (android.os.Build.VERSION.SDK_INT < 14) {return ViewCompat.canScrollVertically(mTarget, -direction) || mTarget.getScrollY() > 0;} else {return ViewCompat.canScrollVertically(mTarget, -direction);}}protected boolean canListViewSroll(AbsListView absListView) {if (mStatus == Status.OPEN) {return absListView.getChildCount() > 0&& (absListView.getFirstVisiblePosition() > 0 || absListView.getChildAt(0).getTop() <absListView.getPaddingTop());} else {final int count = absListView.getChildCount();return count > 0&& (absListView.getLastVisiblePosition() < count - 1|| absListView.getChildAt(count - 1).getBottom() > absListView.getMeasuredHeight());}}@Overrideprotected Parcelable onSaveInstanceState() {SavedState ss = new SavedState(super.onSaveInstanceState());ss.offset = mSlideOffset;ss.status = mStatus.ordinal();return ss;}@Overrideprotected void onRestoreInstanceState(Parcelable state) {SavedState ss = (SavedState) state;super.onRestoreInstanceState(ss.getSuperState());mSlideOffset = ss.offset;mStatus = Status.valueOf(ss.status);if (mStatus == Status.OPEN) {mBehindView.setVisibility(VISIBLE);}requestLayout();}static class SavedState extends BaseSavedState {private float offset;private int status;public SavedState(Parcel source) {super(source);offset = source.readFloat();status = source.readInt();}public SavedState(Parcelable superState) {super(superState);}@Overridepublic void writeToParcel(Parcel out, int flags) {super.writeToParcel(out, flags);out.writeFloat(offset);out.writeInt(status);}public static final Creator<SavedState> CREATOR =new Creator<SavedState>() {public SavedState createFromParcel(Parcel in) {return new SavedState(in);}public SavedState[] newArray(int size) {return new SavedState[size];}};}
}

接下来就是一些Fragment等的页面填充,也没啥好讲的,代码又很多可以优化的地方,在优化的地方,笔者也列出了优化的方案,大家可以根据自己的实际情况做页面级的优化。
附:Android仿京东、天猫商品详情页源码

Android仿京东、天猫商品详情页相关推荐

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

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

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

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

  3. android 网易item广告,Android仿网易严选商品详情页

    仿照网易严选商品详情页面,整个页面分为两个部分,上面一部分是Native的ScrollView,下面一部分则是WebView,其目的是为了可以进行分步加载.滑动到ScrollView底部时,继续向上拖 ...

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

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

  5. android自定义视频列表,Android仿京东天猫列表页播视频看这一篇就足够了

    阅读本文解决什么问题? 解决android 滑动列表页自动播视频中的一些技术难点.助力更好的实现类似需求.不涉及到播放器的具体编解码技术,因为各家用的播放器可能都不一样(其实是我不会~) 何时播视频最 ...

  6. 京东手机商品详情页技术解密

    转载地址:http://geek.csdn.net/news/detail/134812 京东手机单品页在每次大促时承载所有流量的入口,它被天然赋予的一个标签就是抗压,对系统的稳定性.性能方面要求极其 ...

  7. [Android]仿京东手机端类别页

    [Android]仿京东手机端类别页 京东手机端的类别标签页, 是一个左侧滑动可选择类别, 右侧一个类别明细的列表联动页面. 当用户选择左侧选项, 可在右侧显示更多选项来选择. 实现方式也不少. 最常 ...

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

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

  9. 【京东】商品详情页采集

    采集场景 打开京东商品详情页(实例网址:https://item.jd.com/100006607659.html#crumb-wrap ),采集点击不同的参数(颜色.版本等)后得到的数据(商品编号. ...

最新文章

  1. 每周四十小时,你有多少是在为自己干活?
  2. OpenOffce在Centos7安装和使用
  3. VSS源代码管理应该怎么样对bin目录及项目中用到的第三方组件进行管理?
  4. 自动调试自动编译五分钟上手
  5. LeetCode 2202. K 次操作后最大化顶端元素
  6. 信息学奥赛一本通(2046:【例5.15】替换字母)
  7. Python Cookbook by Eric
  8. python实现将文件下内每张图片按顺序命名为txt文本文件中的内容
  9. CANoe教程:CAPL编程
  10. python音频提取pcm_python 实现录音pcm格式功能
  11. 永中云编辑(java版)
  12. 一些著名软件的开发语言概述
  13. windows 命令方式查找指定IP的MAC地址
  14. 【分享】女生教你怎么追MM--送给没有女朋友的来此灌水的GG们
  15. Laravel-excel安装及使用
  16. 工作11年,35岁,裸辞: 感到前途很迷茫,如何破?
  17. MySQL 重置 root 密码以及修改密码时报错password字段不存在
  18. 针对网页开发者的滚动锚定功能
  19. iOS——调用相册换头像
  20. 百度开放平台(文本转化音频)

热门文章

  1. kotlin入门,Android快速转战Kotlin教程,重难点整理
  2. Win11分磁盘怎么分?Win11系统怎么分磁盘?
  3. Android批量添加联系人到通讯录
  4. HZNUOJ 2977 宝可梦决战 种族并查集
  5. 音视频入门 (iOS上fdk-aac的交叉编译)
  6. C语言链表的删除代码实现
  7. python如何定义函数k_Python 函数
  8. 多核CPU 跟 进程/线程
  9. 第16套题目 doc.计算机,计算机二级ms实操题excel难点汇总.doc
  10. javaScript实现通过鼠标滑轮改变元素大小