前言
电商内app,重点在于详情页商品展示,用户不仅要看到图,可以看到各种描述,以及相关规格参数。
有需要做电商类app的童鞋可以看看, 首先先看看效果实现
- 本项目使用的第三方框架:
- 加载网络图片使用的 Fresco
- 头部的商品图轮播 ConvenientBanner
- 导航栏切换 PagerSlidingTabStrip
先看看效果实现
由于代码量过多, 就不一一讲解只介绍几个核心的自定义控件)
不想看的童鞋可以下载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
- 实现滑动到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内容有两个View, mFrontView(***个View)和mBehindView(第二个View)
有两种状态, 状态设置为close就显示***个商品数据View, open状态就显示第二个图文详情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);
这个商品详情页的架构也是本人在已上线的项目中使用