中国领先的IT技术网站
|
|

Android仿京东、天猫app的商品详情页的布局架构, 以及功能实现

电商内app,重点在于详情页商品展示,用户不仅要看到图,可以看到各种描述,以及相关规格参数。

作者:coexist来源:安卓巴士Android开发者门户|2017-01-22 17:55

开发者大赛路演 | 12月16日,技术创新,北京不见不散


前言

电商内app,重点在于详情页商品展示,用户不仅要看到图,可以看到各种描述,以及相关规格参数。

有需要做电商类app的童鞋可以看看, 首先先看看效果实现

  • 本项目使用的第三方框架:
    • 加载网络图片使用的 Fresco
    • 头部的商品图轮播 ConvenientBanner
    • 导航栏切换 PagerSlidingTabStrip

先看看效果实现

由于代码量过多, 就不一一讲解只介绍几个核心的自定义控件)

不想看的童鞋可以下载apk或者在github上下载源码使用

  • github地址
  • apk下载
  • 最外层的布局文件
  1. <?xml version="1.0" encoding="utf-8"?> 
  2. <LinearLayout xmlns:android="http://schemas.android.com/apk/res/android" 
  3.     xmlns:app="http://schemas.android.com/apk/res-auto" 
  4.     xmlns:tools="http://schemas.android.com/tools" 
  5.     android:layout_width="match_parent" 
  6.     android:layout_height="match_parent" 
  7.     android:orientation="vertical"
  8.      <!-- 顶部标题 --> 
  9.     <LinearLayout 
  10.         android:id="@+id/ll_title_root" 
  11.         android:layout_width="match_parent" 
  12.         android:layout_height="wrap_content" 
  13.         android:background="#ec0f38" 
  14.         android:orientation="vertical"
  15.  
  16.         <LinearLayout 
  17.             android:layout_width="match_parent" 
  18.             android:layout_height="44dp" 
  19.             android:orientation="horizontal"
  20.  
  21.             <LinearLayout 
  22.                 android:id="@+id/ll_back" 
  23.                 android:layout_width="wrap_content" 
  24.                 android:layout_height="match_parent" 
  25.                 android:paddingLeft="15dp"
  26.  
  27.                 <ImageView 
  28.                     android:id="@+id/iv_back" 
  29.                     android:layout_width="22dp" 
  30.                     android:layout_height="22dp" 
  31.                     android:layout_gravity="center_vertical" 
  32.                     android:src="@mipmap/address_come_back" /> 
  33.             </LinearLayout> 
  34.  
  35.             <LinearLayout 
  36.                 android:layout_width="0dp" 
  37.                 android:layout_height="match_parent" 
  38.                 android:layout_weight="1" 
  39.                 android:gravity="center"
  40.  
  41.                 <!-- 商品、详情、评价切换的控件 --> 
  42.                 <com.gxz.PagerSlidingTabStrip 
  43.                     android:id="@+id/psts_tabs" 
  44.                     android:layout_width="wrap_content" 
  45.                     android:layout_height="32dp" 
  46.                     android:layout_gravity="center" 
  47.                     android:textColor="#ffffff" 
  48.                     android:textSize="15sp" 
  49.                     app:pstsDividerColor="@android:color/transparent" 
  50.                     app:pstsDividerPaddingTopBottom="0dp" 
  51.                     app:pstsIndicatorColor="#ffffff" 
  • ItemWebView是SlideDetailsLayout的子View (SlideDetailsLayout代码太多, 放到了最后)
    • 功能为显示商品简介的webview
    • 防止往上滑动时会直接滑动到第一个View
    • 实现滑动到WebView顶部时, 让父控件重新获得触摸事件
  1. /** 
  2.  * 商品详情页底部的webview 
  3.  */ 
  4. public class ItemWebView extends WebView { 
  5.     public float oldY; 
  6.     private int t; 
  7.     private float oldX; 
  8.  
  9.     public ItemWebView(Context context) { 
  10.         super(context); 
  11.     } 
  12.  
  13.     public ItemWebView(Context context, AttributeSet attrs) { 
  14.         super(context, attrs); 
  15.     } 
  16.  
  17.     public ItemWebView(Context context, AttributeSet attrs, int defStyleAttr) { 
  18.         super(context, attrs, defStyleAttr); 
  19.     } 
  20.  
  21.  
  22.     @Override 
  23.     public boolean onTouchEvent(MotionEvent ev) { 
  24.  
  25.         switch (ev.getAction()) { 
  26.             case MotionEvent.ACTION_MOVE: 
  27.                 float Y = ev.getY(); 
  28.                 float Ys = Y - oldY; 
  29.                 float X = ev.getX(); 
  30.  
  31.                 //滑动到顶部让父控件重新获得触摸事件 
  32.                 if (Ys > 0 && t == 0) { 
  33.                     getParent().getParent().requestDisallowInterceptTouchEvent(false); 
  34.                 } 
  35.                 break; 
  36.  
  37.             case MotionEvent.ACTION_DOWN: 
  38.                 getParent().getParent().requestDisallowInterceptTouchEvent(true); 
  39.                 oldY = ev.getY(); 
  40.                 oldX = ev.getX(); 
  41.                 break; 
  42.  
  43.             case MotionEvent.ACTION_UP: 
  44.                 getParent().getParent().requestDisallowInterceptTouchEvent(true); 
  45.                 break; 
  46.  
  47.             default
  48.                 break; 
  49.         } 
  50.         return super.onTouchEvent(ev); 
  51.     } 
  52.  
  53.     @Override 
  54.     protected void onScrollChanged(int l, int t, int oldl, int oldt) { 
  55.         this.t = t; 
  56.         super.onScrollChanged(l, t, oldl, oldt); 
  57.     } 
  58.  
  • ItemListView 也是SlideDetailsLayout的子View
    • 和ItemWebView功能大致一样
  1. /** 
  2.  * 商品详情页底部的ListView 
  3.  */ 
  4. public class ItemListView extends ListView implements AbsListView.OnScrollListener { 
  5.     private float oldX, oldY; 
  6.     private int currentPosition; 
  7.  
  8.     public ItemListView(Context context) { 
  9.         super(context); 
  10.         setOnScrollListener(this); 
  11.     } 
  12.  
  13.     public ItemListView(Context context, AttributeSet attrs) { 
  14.         super(context, attrs); 
  15.         setOnScrollListener(this); 
  16.     } 
  17.  
  18.     public ItemListView(Context context, AttributeSet attrs, int defStyleAttr) { 
  19.         super(context, attrs, defStyleAttr); 
  20.         setOnScrollListener(this); 
  21.     } 
  22.  
  23.  
  24.     @Override 
  25.     public boolean onTouchEvent(MotionEvent ev) { 
  26.         switch (ev.getAction()) { 
  27.             case MotionEvent.ACTION_MOVE: 
  28.                 float Y = ev.getY(); 
  29.                 float Ys = Y - oldY; 
  30.                 float X = ev.getX(); 
  31.                 int [] location = new int [2]; 
  32.                 getLocationInWindow(location); 
  33.  
  34.                 //滑动到顶部让父控件重新获得触摸事件 
  35.                 if (Ys > 0 && currentPosition == 0) { 
  36.                     getParent().getParent().requestDisallowInterceptTouchEvent(false); 
  37.                 } 
  38.                 break; 
  39.  
  40.             case MotionEvent.ACTION_DOWN: 
  41.                 getParent().getParent().requestDisallowInterceptTouchEvent(true); 
  42.                 oldY = ev.getY(); 
  43.                 oldX = ev.getX(); 
  44.                 break; 
  45.  
  46.             case MotionEvent.ACTION_UP: 
  47.                 getParent().getParent().requestDisallowInterceptTouchEvent(true); 
  48.                 break; 
  49.  
  50.             default
  51.                 break; 
  52.         } 
  53.         return super.onTouchEvent(ev); 
  54.     } 
  55.  
  56.     @Override 
  57.     public void onScrollStateChanged(AbsListView viewint scrollState) { 
  58.         currentPosition = getFirstVisiblePosition(); 
  59.     } 
  60.  
  61.     @Override 
  62.     public void onScroll(AbsListView viewint firstVisibleItem, int visibleItemCount, int totalItemCount) { 
  63.  
  64.     } 
  • NoScrollViewPager为最外层的父布局
    • 当滑动到图文详情模块时, 能禁止掉ViewPager的滑动事件
  1. /** 
  2.  * 提供禁止滑动功能的自定义ViewPager 
  3.  */ 
  4. public class NoScrollViewPager extends ViewPager { 
  5.     private boolean noScroll = false
  6.  
  7.     public NoScrollViewPager(Context context, AttributeSet attrs) { 
  8.         super(context, attrs); 
  9.     } 
  10.  
  11.  
  12.     public NoScrollViewPager(Context context) { 
  13.         super(context); 
  14.     } 
  15.  
  16.     public void setNoScroll(boolean noScroll) { 
  17.         this.noScroll = noScroll; 
  18.     } 
  19.  
  20.     @Override 
  21.     public void scrollTo(int x, int y) { 
  22.         super.scrollTo(x, y); 
  23.     } 
  24.  
  25.     @Override 
  26.     public boolean onTouchEvent(MotionEvent arg0) { 
  27.         if (noScroll) 
  28.             return false
  29.         else 
  30.             return super.onTouchEvent(arg0); 
  31.     } 
  32.  
  33.     @Override 
  34.     public boolean onInterceptTouchEvent(MotionEvent arg0) { 
  35.         if (noScroll) 
  36.             return false
  37.         else 
  38.             return super.onInterceptTouchEvent(arg0); 
  39.     } 
  40.  
  41.     @Override 
  42.     public void setCurrentItem(int item, boolean smoothScroll) { 
  43.         super.setCurrentItem(item, smoothScroll); 
  44.     } 
  45.  
  46.     @Override 
  47.     public void setCurrentItem(int item) { 
  48.         super.setCurrentItem(item); 
  49.     } 
  50.  

商品模块最外层的布局是一个自定义的ViewGroup名为SlideDetailsLayout

SlideDetailsLayout内容有两个View, mFrontView(第一个View)和mBehindView(第二个View)

有两种状态, 状态设置为close就显示第一个商品数据View, open状态就显示第二个图文详情View

  1. @SuppressWarnings("unused"
  2. public class SlideDetailsLayout extends ViewGroup { 
  3.  
  4.     /** 
  5.      * Callback for panel OPEN-CLOSE status changed. 
  6.      */ 
  7.     public interface OnSlideDetailsListener { 
  8.         /** 
  9.          * Called after status changed. 
  10.          * 
  11.          * @param status {@link Status} 
  12.          */ 
  13.         void onStatucChanged(Status status); 
  14.     } 
  15.  
  16.     public enum Status { 
  17.         /** Panel is closed */ 
  18.         CLOSE
  19.         /** Panel is opened */ 
  20.         OPEN
  21.  
  22.         public static Status valueOf(int stats) { 
  23.             if (0 == stats) { 
  24.                 return CLOSE
  25.             } else if (1 == stats) { 
  26.                 return OPEN
  27.             } else { 
  28.                 return CLOSE
  29.             } 
  30.         } 
  31.     } 
  32.  
  33.     private static final float DEFAULT_PERCENT = 0.2f; 
  34.     private static final int DEFAULT_DURATION = 300; 
  35.  
  36.     private View mFrontView; 
  37.     private View mBehindView; 
  38.  
  39.     private float mTouchSlop; 
  40.     private float mInitMotionY; 
  41.     private float mInitMotionX; 
  42.  
  43.  
  44.     private View mTarget; 
  45.     private float mSlideOffset; 
  46.     private Status mStatus = Status.CLOSE
  47.     private boolean isFirstShowBehindView = true
  48.     private float mPercent = DEFAULT_PERCENT; 
  49.     private long mDuration = DEFAULT_DURATION; 
  50.     private int mDefaultPanel = 0; 
  51.  
  52.     private OnSlideDetailsListener mOnSlideDetailsListener; 
  53.  
  54.     public SlideDetailsLayout(Context context) { 
  55.         this(context, null); 
  56.     } 
  57.  
  58.     public SlideDetailsLayout(Context context, AttributeSet attrs) { 
  59.         this(context, attrs, 0); 
  60.     } 
  61.  
  62.     public SlideDetailsLayout(Context context, AttributeSet attrs, int defStyleAttr) { 
  63.         super(context, attrs, defStyleAttr); 
  64.  
  65.         TypedArray a = context.obtainStyledAttributes(attrs, R.styleable.SlideDetailsLayout, defStyleAttr, 0); 
  66.         mPercent = a.getFloat(R.styleable.SlideDetailsLayout_percent, DEFAULT_PERCENT); 
  67.         mDuration = a.getInt(R.styleable.SlideDetailsLayout_duration, DEFAULT_DURATION); 
  68.         mDefaultPanel = a.getInt(R.styleable.SlideDetailsLayout_default_panel, 0); 

这个商品详情页的架构也是本人在已上线的项目中使用

【编辑推荐】

  1. Android平台的10款开源任务管理和时间追踪应用
  2. Android开发软件架构思考以及经验总结
  3. 看AspectJ在Android中的强势插入
  4. Android性能优化:使用Lint优化代码、去除多余资源
  5. Android键盘面板冲突 布局闪动处理方案
【责任编辑:枯木 TEL:(010)68476606】

点赞 0
分享:
大家都在看
猜你喜欢
24H热文
一周话题
本月最赞

读 书 +更多

网络工程师考试考前冲刺预测卷及考点解析

本书依据最新版《网络工程师考试大纲》的考核要求,深入研究了历年网络工程师考试试题的命题风格和试题结构,对考查的知识点进行了提炼,并...

订阅51CTO邮刊

点击这里查看样刊

订阅51CTO邮刊