Android仿京東、天貓app的商品詳情頁的布局架構, 以及功能實現(xiàn)
前言
電商內(nèi)app,重點在于詳情頁商品展示,用戶不僅要看到圖,可以看到各種描述,以及相關規(guī)格參數(shù)。
有需要做電商類app的童鞋可以看看, 首先先看看效果實現(xiàn)
- 本項目使用的第三方框架:
- 加載網(wǎng)絡圖片使用的 Fresco
- 頭部的商品圖輪播 ConvenientBanner
- 導航欄切換 PagerSlidingTabStrip
先看看效果實現(xiàn)
由于代碼量過多, 就不一一講解只介紹幾個核心的自定義控件)
不想看的童鞋可以下載apk或者在github上下載源碼使用
- github地址
- apk下載
- 最外層的布局文件
- <?xml version="1.0" encoding="utf-8"?>
- <LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
- xmlns:app="http://schemas.android.com/apk/res-auto"
- xmlns:tools="http://schemas.android.com/tools"
- android:layout_width="match_parent"
- android:layout_height="match_parent"
- android:orientation="vertical">
- <!-- 頂部標題 -->
- <LinearLayout
- android:id="@+id/ll_title_root"
- android:layout_width="match_parent"
- android:layout_height="wrap_content"
- android:background="#ec0f38"
- android:orientation="vertical">
- <LinearLayout
- android:layout_width="match_parent"
- android:layout_height="44dp"
- android:orientation="horizontal">
- <LinearLayout
- android:id="@+id/ll_back"
- android:layout_width="wrap_content"
- android:layout_height="match_parent"
- android:paddingLeft="15dp">
- <ImageView
- android:id="@+id/iv_back"
- android:layout_width="22dp"
- android:layout_height="22dp"
- android:layout_gravity="center_vertical"
- android:src="@mipmap/address_come_back" />
- </LinearLayout>
- <LinearLayout
- android:layout_width="0dp"
- android:layout_height="match_parent"
- android:layout_weight="1"
- android:gravity="center">
- <!-- 商品、詳情、評價切換的控件 -->
- <com.gxz.PagerSlidingTabStrip
- android:id="@+id/psts_tabs"
- android:layout_width="wrap_content"
- android:layout_height="32dp"
- android:layout_gravity="center"
- android:textColor="#ffffff"
- android:textSize="15sp"
- app:pstsDividerColor="@android:color/transparent"
- app:pstsDividerPaddingTopBottom="0dp"
- app:pstsIndicatorColor="#ffffff"
- ItemWebView是SlideDetailsLayout的子View (SlideDetailsLayout代碼太多, 放到了***)
- 功能為顯示商品簡介的webview
- 防止往上滑動時會直接滑動到***個View
- 實現(xiàn)滑動到WebView頂部時, 讓父控件重新獲得觸摸事件
- /**
- * 商品詳情頁底部的webview
- */
- public class ItemWebView extends WebView {
- public float oldY;
- private int t;
- private float oldX;
- public ItemWebView(Context context) {
- super(context);
- }
- public ItemWebView(Context context, AttributeSet attrs) {
- super(context, attrs);
- }
- public ItemWebView(Context context, AttributeSet attrs, int defStyleAttr) {
- super(context, attrs, defStyleAttr);
- }
- @Override
- public boolean onTouchEvent(MotionEvent ev) {
- switch (ev.getAction()) {
- case MotionEvent.ACTION_MOVE:
- float Y = ev.getY();
- float Ys = Y - oldY;
- float X = ev.getX();
- //滑動到頂部讓父控件重新獲得觸摸事件
- if (Ys > 0 && t == 0) {
- getParent().getParent().requestDisallowInterceptTouchEvent(false);
- }
- break;
- case MotionEvent.ACTION_DOWN:
- getParent().getParent().requestDisallowInterceptTouchEvent(true);
- oldY = ev.getY();
- oldX = ev.getX();
- break;
- case MotionEvent.ACTION_UP:
- getParent().getParent().requestDisallowInterceptTouchEvent(true);
- break;
- default:
- break;
- }
- return super.onTouchEvent(ev);
- }
- @Override
- protected void onScrollChanged(int l, int t, int oldl, int oldt) {
- this.t = t;
- super.onScrollChanged(l, t, oldl, oldt);
- }
- }
- ItemListView 也是SlideDetailsLayout的子View
- 和ItemWebView功能大致一樣
- /**
- * 商品詳情頁底部的ListView
- */
- public class ItemListView extends ListView implements AbsListView.OnScrollListener {
- private float oldX, oldY;
- private int currentPosition;
- public ItemListView(Context context) {
- super(context);
- setOnScrollListener(this);
- }
- public ItemListView(Context context, AttributeSet attrs) {
- super(context, attrs);
- setOnScrollListener(this);
- }
- public ItemListView(Context context, AttributeSet attrs, int defStyleAttr) {
- super(context, attrs, defStyleAttr);
- setOnScrollListener(this);
- }
- @Override
- public boolean onTouchEvent(MotionEvent ev) {
- switch (ev.getAction()) {
- case MotionEvent.ACTION_MOVE:
- float Y = ev.getY();
- float Ys = Y - oldY;
- float X = ev.getX();
- int [] location = new int [2];
- getLocationInWindow(location);
- //滑動到頂部讓父控件重新獲得觸摸事件
- if (Ys > 0 && currentPosition == 0) {
- getParent().getParent().requestDisallowInterceptTouchEvent(false);
- }
- break;
- case MotionEvent.ACTION_DOWN:
- getParent().getParent().requestDisallowInterceptTouchEvent(true);
- oldY = ev.getY();
- oldX = ev.getX();
- break;
- case MotionEvent.ACTION_UP:
- getParent().getParent().requestDisallowInterceptTouchEvent(true);
- break;
- default:
- break;
- }
- return super.onTouchEvent(ev);
- }
- @Override
- public void onScrollStateChanged(AbsListView view, int scrollState) {
- currentPosition = getFirstVisiblePosition();
- }
- @Override
- public void onScroll(AbsListView view, int firstVisibleItem, int visibleItemCount, int totalItemCount) {
- }
- }
- NoScrollViewPager為最外層的父布局
- 當滑動到圖文詳情模塊時, 能禁止掉ViewPager的滑動事件
- /**
- * 提供禁止滑動功能的自定義ViewPager
- */
- public class NoScrollViewPager extends ViewPager {
- private boolean noScroll = false;
- public NoScrollViewPager(Context context, AttributeSet attrs) {
- super(context, attrs);
- }
- public NoScrollViewPager(Context context) {
- super(context);
- }
- public void setNoScroll(boolean noScroll) {
- this.noScroll = noScroll;
- }
- @Override
- public void scrollTo(int x, int y) {
- super.scrollTo(x, y);
- }
- @Override
- public boolean onTouchEvent(MotionEvent arg0) {
- if (noScroll)
- return false;
- else
- return super.onTouchEvent(arg0);
- }
- @Override
- public boolean onInterceptTouchEvent(MotionEvent arg0) {
- if (noScroll)
- return false;
- else
- return super.onInterceptTouchEvent(arg0);
- }
- @Override
- public void setCurrentItem(int item, boolean smoothScroll) {
- super.setCurrentItem(item, smoothScroll);
- }
- @Override
- public void setCurrentItem(int item) {
- super.setCurrentItem(item);
- }
- }
商品模塊最外層的布局是一個自定義的ViewGroup名為SlideDetailsLayout
SlideDetailsLayout內(nèi)容有兩個View, mFrontView(***個View)和mBehindView(第二個View)
有兩種狀態(tài), 狀態(tài)設置為close就顯示***個商品數(shù)據(jù)View, open狀態(tài)就顯示第二個圖文詳情View
- @SuppressWarnings("unused")
- public class SlideDetailsLayout extends ViewGroup {
- /**
- * Callback for panel OPEN-CLOSE status changed.
- */
- public interface OnSlideDetailsListener {
- /**
- * Called after status changed.
- *
- * @param status {@link Status}
- */
- void onStatucChanged(Status status);
- }
- public enum Status {
- /** Panel is closed */
- CLOSE,
- /** Panel is opened */
- 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);
這個商品詳情頁的架構也是本人在已上線的項目中使用