最新国产好看的视频,伊人天堂AV在线,国产Aaaaaa视频,蜜臀视频在线观看一区,人妻av色图,密臀久久久精品影片,青青视频免费观看毛片,久草在线观看视,国产三级精品色情在线

Android仿京東、天貓商品詳情頁(yè)

 更新時(shí)間:2017年01月22日 17:16:22   作者:code_xzh  
這篇文章主要為大家詳細(xì)介紹了Android仿京東、天貓商品詳情頁(yè)的相關(guān)資料,具有一定的參考價(jià)值,感興趣的小伙伴們可以參考一下

前言

前面在介紹控件TabLayout控件和CoordinatorLayout使用的時(shí)候說(shuō)了下實(shí)現(xiàn)京東、天貓?jiān)斍轫?yè)面的效果,今天要說(shuō)的是優(yōu)化版,是我們線上實(shí)現(xiàn)的效果,首先看一張效果:

這里寫圖片描述
這里寫圖片描述

項(xiàng)目結(jié)構(gòu)分析

首先我們來(lái)分析一下要實(shí)現(xiàn)上面的效果,我們需要怎么做。頂部是一個(gè)可以滑動(dòng)切換Tab,可以用ViewPager+Fragment實(shí)現(xiàn),也可以使用系統(tǒng)的TabLayout控件實(shí)現(xiàn);而下面的 View是一個(gè)可以滑動(dòng)拖動(dòng)效果的View,可以采用網(wǎng)上一個(gè)叫做DragLayout的控件,我這里是自己實(shí)現(xiàn)了一個(gè),主要是通過(guò)對(duì)View的事件分發(fā)的一些處理;然后滑動(dòng)到下面就是一個(gè)圖文詳情的View(Fragment),本頁(yè)面包含兩個(gè)界面:詳情頁(yè)面和參數(shù)頁(yè)面;最后是評(píng)價(jià)的View(Fragment)。經(jīng)過(guò)上面的分析,我們的界面至少需要4個(gè)Fragement,首先來(lái)看一下項(xiàng)目結(jié)構(gòu):

這里寫圖片描述

代碼講解

代碼比較多,這里只講解幾個(gè)核心的方法類。首先我們來(lái)看一下我們自己是的這個(gè)具有阻尼效果的View,我們知道要實(shí)現(xiàn)的效果,我們需要對(duì)View的事件做一個(gè)全面的實(shí)現(xiàn)。這里首先說(shuō)一下View的事件分發(fā)的流程:

onInterceptTouchEvent()–>dispatchTouchEvent()–>onTouchEvent();

首先我們需要對(duì)View傳過(guò)來(lái)的事件做一個(gè)攔截:

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;

最后轉(zhuǎn)發(fā)給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;

滑動(dòng)事件完了之后我們需要調(diào)用request方法對(duì)View做一個(gè)重繪:

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);
  }

上下滑動(dòng)也是涉及到兩個(gè)界面:mFrontView和mBehindView,然后通過(guò)判斷滑動(dòng)事件來(lái)顯示哪一個(gè)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);
  }
 }


 @Override
 protected LayoutParams generateDefaultLayoutParams() {
  return new MarginLayoutParams(MarginLayoutParams.WRAP_CONTENT, MarginLayoutParams.WRAP_CONTENT);
 }

 @Override
 public LayoutParams generateLayoutParams(AttributeSet attrs) {
  return new MarginLayoutParams(getContext(), attrs);
 }

 @Override
 protected LayoutParams generateLayoutParams(LayoutParams p) {
  return new MarginLayoutParams(p);
 }

 @Override
 protected 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() {
    @Override
    public void run() {
     smoothOpen(false);
    }
   });
  }
 }

 @Override
 protected 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);
 }

 @Override
 protected 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);
  }
 }

 @Override
 public 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;
 }

 @Override
 public 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 down
   if (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() {
   @Override
   public void onAnimationUpdate(ValueAnimator animation) {
    mSlideOffset = (float) animation.getAnimatedValue();
    requestLayout();
   }
  });
  animator.addListener(new AnimatorListenerAdapter() {
   @Override
   public 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());
  }
 }

 @Override
 protected Parcelable onSaveInstanceState() {
  SavedState ss = new SavedState(super.onSaveInstanceState());
  ss.offset = mSlideOffset;
  ss.status = mStatus.ordinal();
  return ss;
 }

 @Override
 protected 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);
  }

  @Override
  public 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];
     }
    };
 }
}

接下來(lái)就是一些Fragment等的頁(yè)面填充,也沒(méi)啥好講的,代碼又很多可以優(yōu)化的地方,在優(yōu)化的地方,筆者也列出了優(yōu)化的方案,大家可以根據(jù)自己的實(shí)際情況做頁(yè)面級(jí)的優(yōu)化。

源碼下載:http://xiazai.jb51.net/201701/yuanma/AndriodGoodDetail(jb51.net).rar

以上就是本文的全部?jī)?nèi)容,希望對(duì)大家的學(xué)習(xí)有所幫助,也希望大家多多支持腳本之家。

相關(guān)文章

  • Android 用Time和Calendar獲取系統(tǒng)當(dāng)前時(shí)間源碼分享(年月日時(shí)分秒周幾)

    Android 用Time和Calendar獲取系統(tǒng)當(dāng)前時(shí)間源碼分享(年月日時(shí)分秒周幾)

    這篇文章主要介紹了Android 用Time和Calendar獲取系統(tǒng)當(dāng)前時(shí)間源碼分享,包括年月日時(shí)分秒周幾的源碼,非常不錯(cuò),具有參考借鑒價(jià)值,需要的朋友參考下
    2017-01-01
  • Android時(shí)間對(duì)話框使用方法詳解

    Android時(shí)間對(duì)話框使用方法詳解

    這篇文章主要為大家詳細(xì)介紹了Android時(shí)間對(duì)話框的使用方法,包括analogclock和digitalclock顯示時(shí)鐘的控件,具有一定的參考價(jià)值,感興趣的小伙伴們可以參考一下
    2017-08-08
  • Android?Camera1實(shí)現(xiàn)預(yù)覽框顯示

    Android?Camera1實(shí)現(xiàn)預(yù)覽框顯示

    這篇文章主要為大家詳細(xì)介紹了Android?Camera1實(shí)現(xiàn)預(yù)覽框顯示,文中示例代碼介紹的非常詳細(xì),具有一定的參考價(jià)值,感興趣的小伙伴們可以參考一下
    2022-05-05
  • Android實(shí)現(xiàn)蒙版彈出框效果

    Android實(shí)現(xiàn)蒙版彈出框效果

    這篇文章主要為大家詳細(xì)介紹了Android實(shí)現(xiàn)蒙版彈出框效果,文中示例代碼介紹的非常詳細(xì),具有一定的參考價(jià)值,感興趣的小伙伴們可以參考一下
    2017-05-05
  • android完美實(shí)現(xiàn) 拍照 選擇圖片 剪裁等代碼分享

    android完美實(shí)現(xiàn) 拍照 選擇圖片 剪裁等代碼分享

    本文給大家分享了2個(gè)安卓實(shí)現(xiàn)實(shí)現(xiàn) 拍照 選擇圖片 剪裁等的代碼,都是從正式項(xiàng)目中提取出來(lái)了,非常實(shí)用,有需要的小伙伴可以參考下。
    2016-01-01
  • Android兩種輪詢的實(shí)現(xiàn)方法

    Android兩種輪詢的實(shí)現(xiàn)方法

    這篇文章主要為大家詳細(xì)介紹了Android兩種輪詢的實(shí)現(xiàn)方法,文中示例代碼介紹的非常詳細(xì),具有一定的參考價(jià)值,感興趣的小伙伴們可以參考一下
    2020-06-06
  • 詳解android與百度echarts項(xiàng)目整合方法

    詳解android與百度echarts項(xiàng)目整合方法

    在本篇文章里我們給大家分享了關(guān)于android與百度echarts項(xiàng)目整合方法和具體步驟,需要的朋友們跟著學(xué)習(xí)下。
    2019-03-03
  • Android獲取位置信息的方法

    Android獲取位置信息的方法

    這篇文章主要為大家詳細(xì)介紹了Android獲取位置信息的方法,具有一定的參考價(jià)值,感興趣的小伙伴們可以參考一下
    2019-05-05
  • Flutter Map常用操作方法總結(jié)

    Flutter Map常用操作方法總結(jié)

    Flutter 中的 Map 是一種鍵值對(duì)的集合,可以存儲(chǔ)任意類型的數(shù)據(jù),并且可以通過(guò)鍵來(lái)訪問(wèn)和操作對(duì)應(yīng)的值,下面我們就來(lái)學(xué)習(xí)一下Flutter Map的常用操作方法吧
    2023-11-11
  • 關(guān)于AndroidStudio R文件莫名其妙缺失的快速解決方法

    關(guān)于AndroidStudio R文件莫名其妙缺失的快速解決方法

    下面小編就為大家?guī)?lái)一篇關(guān)于AndroidStudio R文件莫名其妙缺失的快速解決方法。小編覺(jué)得挺不錯(cuò)的,現(xiàn)在就分享給大家,也給大家做個(gè)參考。一起跟隨小編過(guò)來(lái)看看吧
    2017-03-03

最新評(píng)論

曲周县| 泾源县| 原阳县| 高阳县| 綦江县| 民和| 巴彦淖尔市| 诸城市| 基隆市| 合作市| 张北县| 海口市| 香港| 临邑县| 郎溪县| 惠安县| 广宁县| 静安区| 顺平县| 洪湖市| 茌平县| 绿春县| 太仓市| 保康县| 南部县| 阿巴嘎旗| 永善县| 阿巴嘎旗| 瑞金市| 子长县| 鄂托克前旗| 隆昌县| 武清区| 浙江省| 郴州市| 沿河| 金华市| 施甸县| 四川省| 玛多县| 出国|