当前位置: 移动技术网 > IT编程>移动开发>Android > Android仿京东、天猫商品详情页

Android仿京东、天猫商品详情页

2019年07月24日  | 移动技术网IT编程  | 我要评论

给老板的新年祝福语,美藤稻森,从零开始的异世界生活动漫

前言

前面在介绍控件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);
  }
 }


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

接下来就是一些fragment等的页面填充,也没啥好讲的,代码又很多可以优化的地方,在优化的地方,笔者也列出了优化的方案,大家可以根据自己的实际情况做页面级的优化。

源码下载:

以上就是本文的全部内容,希望对大家的学习有所帮助,也希望大家多多支持移动技术网。

如对本文有疑问,请在下面进行留言讨论,广大热心网友会与你互动!! 点击进行留言回复

相关文章:

验证码:
移动技术网