UI-View和自定义View

DecorView层级

DecorView层级

View

View的绘制流程

ViewRoot对应于ViewRootImpl类,它是连接WindowManagerDecorView纽带View的三大流程均是通过ViewRoot来完成的。在ActivityThread中,当Activity对象被创建完毕后,会将DecorView添加到Window中,同时会创建ViewRootImpl对象,并将ViewRootImpl对象和DecorView建立关联

View的整个绘制流程可以分为以下三个阶段:

  • measure: 判断是否需要重新计算View的大小,需要的话则计算
  • layout: 判断是否需要重新计算View的位置,需要的话则计算
  • draw: 判断是否需要重新绘制View,需要的话则重绘制

performTraversals工作流程

View-Post

MeasureSpec

MeasureSpec表示的是一个32位的整型值,它的高2位表示测量模式SpecMode,第30位表示某种测量模式下的规格大小SpecSize。MeasureSpec是View类的一个静态内部类,用来说明应该如何测量这个View

Mode 说明
UNSPECIFIED 不指定测量模式, 父视图没有限制子视图的大小,子视图可以是想要的任何尺寸,通常用于系统内部,应用开发中很少用到。
EXACTLY 精确测量模式,视图宽高指定为match_parent或具体数值时生效,表示父视图已经决定了子视图的精确大小,这种模式下View的测量值就是SpecSize的值
AT_MOST 最大值测量模式,当视图的宽高指定为wrap_content时生效,此时子视图的尺寸可以是不超过父视图允许的最大尺寸的任何尺寸

对于DecorView而言,它的MeasureSpec由窗口尺寸和其自身的LayoutParams共同决定;对于普通的View,它的MeasureSpec由父视图的MeasureSpec和其自身的LayoutParams共同决定

childLayoutParams\parentSpecMode EXACTLY AT_MOST
dp/px EXACTLY(childSize) EXACTLY(childSize)
match_parent EXACTLY(childSize) AT_MOST(parentSize)
wrap_content AT_MOST(parentSize) AT_MOST(parentSize)

直接继承View的控件需要重写onMeasure方法并设置wrap_content时的自身大小,因为View在布局中使用wrap_content,那么它的specMode是AT_MOST模式,在这种模式下,它的宽/高等于父容器当前剩余的空间大小,就相当于使用match_parent。这解决方式如下:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
super.onMeasure(widthMeasureSpec, heightMeasureSpec);
int widthSpecMode = MeasureSpec.getMode(widthMeasureSpec);
int widtuhSpecSize = MeasureSpec.getSize(widthMeasureSpec);
int heightSpecMode = MeasureSpec.getMode(heightMeasureSpec);
int heightSpecSize = MeasureSpec.getSize(heightMeasureSpec);
// 在wrap_content的情况下指定内部宽/高(mWidth和mHeight)8u
if (widthSpecMode == MeasureSpec.AT_MOST && heightSpecMode == MeasureSpec.AT_MOST) {
setMeasureDimension(mWidth, mHeight);
} else if (widthSpecMode == MeasureSpec.AT_MOST) {
setMeasureDimension(mWidth, heightSpecSize);
} else if (heightSpecMode == MeasureSpec.AT_MOST) {
setMeasureDimension(widthSpecSize, mHeight);
}
}

MotionEvent

事件 说明
ACTION_DOWN 手指刚接触到屏幕
ACTION_MOVE 手指在屏幕上移动
ACTION_UP 手机从屏幕上松开的一瞬间
ACTION_CANCEL 触摸事件取消

点击屏幕后松开,事件序列为 DOWN -> UP,点击屏幕滑动松开,事件序列为 DOWN -> MOVE -> …> MOVE -> UP。

getX/getY 返回相对于当前View左上角的坐标,getRawX/getRawY 返回相对于屏幕左上角的坐标

TouchSlop是系统所能识别出的被认为滑动的最小距离,不同设备值可能不相同,可通过 ViewConfiguration.get(getContext()).getScaledTouchSlop() 获取。

VelocityTracker

VelocityTracker 可用于追踪手指在滑动中的速度:

1
2
3
4
5
6
7
8
9
10
11
12
13
view.setOnTouchListener(new View.OnTouchListener() {
@Override
public boolean onTouch(View v, MotionEvent event) {
VelocityTracker velocityTracker = VelocityTracker.obtain();
velocityTracker.addMovement(event);
velocityTracker.computeCurrentVelocity(1000);
int xVelocity = (int) velocityTracker.getXVelocity();
int yVelocity = (int) velocityTracker.getYVelocity();
velocityTracker.clear();
velocityTracker.recycle();
return false;
}
});

GestureDetector

GestureDetector 辅助检测用户的单击、滑动、长按、双击等行为:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
final GestureDetector mGestureDetector = new GestureDetector(this, new GestureDetector.OnGestureListener() {
@Override
public boolean onDown(MotionEvent e) { return false; }

@Override
public void onShowPress(MotionEvent e) { }

@Override
public boolean onSingleTapUp(MotionEvent e) { return false; }

@Override
public boolean onScroll(MotionEvent e1, MotionEvent e2, float distanceX, float distanceY) { return false; }

@Override
public void onLongPress(MotionEvent e) { }

@Override
public boolean onFling(MotionEvent e1, MotionEvent e2, float velocityX, float velocityY) { return false; }
});
mGestureDetector.setOnDoubleTapListener(new OnDoubleTapListener() {
@Override
public boolean onSingleTapConfirmed(MotionEvent e) { return false; }

@Override
public boolean onDoubleTap(MotionEvent e) { return false; }

@Override
public boolean onDoubleTapEvent(MotionEvent e) { return false; }
});
// 解决长按屏幕后无法拖动的问题
mGestureDetector.setIsLongpressEnabled(false);
imageView.setOnTouchListener(new View.OnTouchListener() {
@Override
public boolean onTouch(View v, MotionEvent event) {
return mGestureDetector.onTouchEvent(event);
}
});

如果是监听滑动相关,建议在 onTouchEvent 中实现,如果要监听双击,那么就使用 GestureDectector

Scroller

弹性滑动对象,用于实现 View 的弹性滑动,Scroller 本身无法让 View 弹性滑动,需要和 View 的 computeScroll 方法配合使用。startScroll 方法是无法让 View 滑动的,invalidate 会导致 View 重绘,重回后会在 draw 方法中又会去调用 computeScroll 方法,computeScroll 方法又会去向 Scroller 获取当前的 scrollX 和 scrollY,然后通过 scrollTo 方法实现滑动,接着又调用 postInvalidate 方法如此反复。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
Scroller mScroller = new Scroller(mContext);

private void smoothScrollTo(int destX) {
int scrollX = getScrollX();
int delta = destX - scrollX;
// 1000ms 内滑向 destX,效果就是慢慢滑动
mScroller.startScroll(scrollX, 0 , delta, 0, 1000);
invalidate();
}

@Override
public void computeScroll() {
if (mScroller.computeScrollOffset()) {
scrollTo(mScroller.getCurrX(), mScroller.getCurrY());
postInvalidate();
}
}

View的滑动

  • scrollTo/scrollBy
    适合对 View 内容的滑动。scrollBy 实际上也是调用了 scrollTo 方法:

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    public void scrollTo(int x, int y) {
    if (mScrollX != x || mScrollY != y) {
    int oldX = mScrollX;
    int oldY = mScrollY;
    mScrollX = x;
    mScrollY = y;
    invalidateParentCaches();
    onScrollChanged(mScrollX, mScrollY, oldX, oldY);
    if (!awakenScrollBars()) {
    postInvalidateOnAnimation();
    }
    }
    }

    public void scrollBy(int x, int y) {
    scrollTo(mScrollX + x, mScrollY + y);
    }

    mScrollX的值等于 View 的左边缘和 View 内容左边缘在水平方向的距离,mScrollY的值等于 View 上边缘和 View 内容上边缘在竖直方向的距离。scrollToscrollBy 只能改变 View 内容的位置而不能改变 View 在布局中的位置。

  • 使用动画
    操作简单,主要适用于没有交互的 View 和实现复杂的动画效果。

  • 改变布局参数
    操作稍微复杂,适用于有交互的 View.

    1
    2
    3
    4
    5
    ViewGroup.MarginLayoutParams params = (ViewGroup.MarginLayoutParams) view.getLayoutParams();
    params.width += 100;
    params.leftMargin += 100;
    view.requestLayout();
    //或者 view.setLayoutParams(params);

View的事件分发

点击事件达到顶级 View(一般是一个ViewGroup),会调用 ViewGroup 的 dispatchTouchEvent 方法,如果顶级 ViewGroup 拦截事件即 onInterceptTouchEvent 返回 true,则事件由 ViewGroup 处理,这时如果 ViewGroup 的 mOnTouchListener 被设置,则 onTouch 会被调用,否则 onTouchEvent 会被调用。也就是说如果都提供的话,onTouch 会屏蔽掉 onTouchEvent。在 onTouchEvent 中,如果设置了 mOnClickListenser,则 onClick 会被调用。如果顶级 ViewGroup 不拦截事件,则事件会传递给它所在的点击事件链上的子 View,这时子 View 的 dispatchTouchEvent会被调用。如此循环。

安卓/事件拦截情况

  • ViewGroup 默认不拦截任何事件。ViewGroup 的 onInterceptTouchEvent 方法默认返回 false。
  • View 没有 onInterceptTouchEvent 方法,一旦有点击事件传递给它,onTouchEvent 方法就会被调用。
  • View 在可点击状态下,onTouchEvent 默认会消耗事件。
  • ACTION_DOWN 被拦截了,onInterceptTouchEvent 方法执行一次后,就会留下记号(mFirstTouchTarget == null)那么往后的 ACTION_MOVE 和 ACTION_UP 都会拦截。

    事件分发机制源码

    事件分发机制

在Activity中获取某个View的宽高

  • Activity/View#onWindowFocusChanged
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    // 此时View已经初始化完毕
    // 当Activity的窗口得到焦点和失去焦点时均会被调用一次
    // 如果频繁地进行onResume和onPause,那么onWindowFocusChanged也会被频繁地调用
    public void onWindowFocusChanged(boolean hasFocus) {
    super.onWindowFocusChanged(hasFocus);
    if (hasFocus) {
    int width = view.getMeasureWidth();
    int height = view.getMeasuredHeight();
    }
    }
  • view.post(runnable)
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    // 通过post可以将一个runnable投递到消息队列的尾部,// 然后等待Looper调用次runnable的时候,View也已经初
    // 始化好了
    protected void onStart() {
    super.onStart();
    view.post(new Runnable() {

    @Override
    public void run() {
    int width = view.getMeasuredWidth();
    int height = view.getMeasuredHeight();
    }
    });
    }
  • ViewTreeObserver
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    // 当View树的状态发生改变或者View树内部的View的可见// 性发生改变时,onGlobalLayout方法将被回调
    protected void onStart() {
    super.onStart();

    ViewTreeObserver observer = view.getViewTreeObserver();
    observer.addOnGlobalLayoutListener(new OnGlobalLayoutListener() {

    @SuppressWarnings("deprecation")
    @Override
    public void onGlobalLayout() {
    view.getViewTreeObserver().removeGlobalOnLayoutListener(this);
    int width = view.getMeasuredWidth();
    int height = view.getMeasuredHeight();
    }
    });
    }

Draw的基本流程

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
// 绘制基本上可以分为六个步骤
public void draw(Canvas canvas) {
...
// 步骤一:绘制View的背景
drawBackground(canvas);
...
// 步骤二:如果需要的话,保持canvas的图层,为fading做准备
saveCount = canvas.getSaveCount();
...
canvas.saveLayer(left, top, right, top + length, null, flags);
...
// 步骤三:绘制View的内容
onDraw(canvas);
...
// 步骤四:绘制View的子View
dispatchDraw(canvas);
...
// 步骤五:如果需要的话,绘制View的fading边缘并恢复图层
canvas.drawRect(left, top, right, top + length, p);
...
canvas.restoreToCount(saveCount);
...
// 步骤六:绘制View的装饰(例如滚动条等等)
onDrawForeground(canvas)
}

View绘制从ViewRootImpl的performTraversals()说起以LinearLayout为例

View绘制从ViewRootImpl的performTraversals()说起以LinearLayout为例

View绘制流程-从activityThread的handleResumeActivity说起

UI-View和自定义View/View绘制流程-从activityThread的handleResumeActivity说起

从invalidate到onDraw

从invalidate到onDraw

View的事件体系

《Android开发艺术探索》第3章 View的事件体系

View基础知识

什么是View

View类图.png

View的位置参数

View的位置主要由它的四个顶点来决定,对应View的四个属性:top(左上角纵坐标)、left(左上角横坐标)、right(右下角横坐标)、bottom(右下角纵坐标),这些坐标是相对View的父容器来说的,是相对坐标。

Android中,x轴和y轴的正方向分别是右和下

View的位置坐标和父容器的关系.png

View的 width = right-left

View的 height = bottom-top

Left = getLeft();

Right = getRight();

Top = getTop();

Bottom = getBottom();

x和y是View的左上角坐标(x = left + translationX; y = top + translationY)

translationX和translationY是View左上角相对于父容器的偏移量(默认是0)

注意:View平移的过程中,top和left表示的是原始左上角的位置信息,其值不会发生改变。 改变的是x、y、translationX和translationY

MotionEvent、TouchSlop

MotionEvent有ACTION_DOWN、ACTION_MOVE、ACTION_UP事件

点击屏幕后松开:DOWN–>UP

点击屏幕滑动再松开:DOWN–>MOVE–>…–>MOVE–>UP

getX/getY是当前View左上角的x和y坐标

getRawX和getRawY是相对于手机屏幕左上角的x和y坐标

VelocityTracker

GestureDetector

Scroller对象

自定义View

  • 继承View重写onDraw方法
    主要用于实现一些不规则的效果,静态或者动态地显示一些不规则的图形,即重写onDraw方法。采用这种方式需要自己支持wrap_content,并且padding也需要自己处理。

  • 继承ViewGroup派生特殊的Layout
    主要用于实现自定义布局,采用这种方式需要合适地处理ViewGroup的测量、布局两个过程,并同时处理子元素的测量和布局过程。

  • 继承特定的View
    用于扩张某种已有的View的功能

  • 继承特定的ViewGroup
    用于扩张某种已有的ViewGroup的功能

继承ViewGroup的自定义View

多个地方引用同一个布局的情况下,可以把此布局抽取到一个单独的xml中,在使用的地方用<include>来引入。

抽取出的布局可以使用自定义View,方便统一管理属性(不同在各个activity/fragment(各自的布局文件中include了这个抽取出的布局)中写这些逻辑)

例子:自定义个状态栏

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
public class TitleLayout extends LinearLayout{
public TitleLayout(Context context, AttributeSet attrs){
super(context, attrs);
LayoutInflater.from(context).inflate(R.layout.title, this);
Button titleBack = (Button) findViewById(R.id.title_back);
Button titleEdit = (Button) findViewById(R.id.tile_edit);
titleBack.setOnClickListener(new OnClickListener(){
@Override
public void onClick(View v){
((Activity) getContext()).finish();
}
});
titleEdit.setOnClickListener(new OnClickListener(){
@Override
public void onClick(View v){
Toast.makeText(getContext(), "You clicked Edit button", Toast.LENGTH_SHORT).show();
}
});
}
}
1
2
3
4
5
6
7
8
<LinearLayout
xmlns:android="http://schemas.android.com/apk/res/android"
android:layout_width="match_parent"
android:layout_height="match_parent">
<com.example.uicustomviews.TitleLayout
android:layout_width="match_parent"
android:layout_height="wrap_content"/>
</LinearLayout>

属性动画(ValueAnimator)

属性动画的强大之处在于可以对任意对象的任意属性增加动画效果,并且可以自定义值的类型和变化过程(TypeEvaluator)和过渡速度(Interpolator)

  • 最常用的是 ObjectAnimator就是(ValueAnimator)的子类。
  • ValueAnimator以特定的方式(可以自定义)对值机型不断的修改,已达到某种想要的过渡效果。它提供设置播放次数、动画间隔、重复模式、开始动画以及设置动画监听器的方法。
    例子:从 0 到 1,耗时 3000 毫秒的线性变化。监听器中输出值
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    ValueAnimator animator = 
    ValueAnimator.ofFloat(0f,1.0f);
    animator.setDuration(3000);
    animator.setInterpolator(new LinearInterpolator());
    //添加监听器获取具体改变的值
    animator.addUpdateListener(new ValueAnimator.AnimatorUpdateListener(){
    @Override
    public void onAnimationUpdate(ValueAnimator animation){
    float value = (Float) animation.getAnimatedValue();
    Log.d(TAG, value);
    }
    });
    animator.start();
    例子:线性动画,左右来回移动
    1
    2
    3
    4
    5
    @Override
    protected void onDraw(Canvas canvas){
    super.onDraw(canvas);
    canvas.drawCircle(XPoint, heightSpecSize / 2, 30, mPaint);
    }
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    public void start(){
    final ValueAnimator animator = ValueAnimator.ofFloat(60, 600);
    animator.setDuration(2000);
    animator.setRepeatCount(ValueAnimator.INFINITE);
    animator.setRepeatMode(ValueAnimator.RESTART);
    animator.setInterpolator(new LinearInterpolator());
    animator.addUpdateListener(new ValueAnimator.AnimatorUpdateListener(){
    @Override
    public void onAnimationUpdate(ValueAnimator animation){
    //获取到动画每次改变的 float 值,赋值给 xpoint
    XPoint = (Float) animation.getAnimatedValue();
    //通知 view 重绘
    invalidate();
    }
    });
    animator.start();
    }
  • ValueAnimator.ofFloat(float…),还有 ofInt(int…)。它们内部已经使用了 FloatEvaluator、FloatArrayEvaluator、IntEvaluator、IntArrayEvaluator 这些系统已经实现好了的 TypeEvaluator。
  • ValueAniamtor.ofObject(TypeEvaluator, Object…)第一个参数必须是 TypeEvaluator,用于设定自定义类型。
    例子:
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    class PointEvaluator implements TypeEvaluator{

    @Override
    public Object evaluate(float fraction, Object startValue, Object endValue) {
    Point startPoint = (Point) startValue;
    Point endPoint = (Point) endValue;

    int x = (int) (startPoint.x + fraction * (endPoint.x - startPoint.x));
    int y = (int) (startPoint.y + fraction * (endPoint.y - startPoint.y));

    return new Point(x, y);
    }
    }
    1
    2
    3
    4
    protected void onDraw(Canvas canvas) {
    super.onDraw(canvas);
    canvas.drawCircle(mPoint.x, mPoint.y, 30, mPaint);
    }
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    public void start() {
    final ValueAnimator animator = ValueAnimator.ofObject(new PointEvaluator(),
    new Point(30, 30), new Point(600, 600));
    animator.setDuration(2000);
    animator.setRepeatCount(ValueAnimator.INFINITE);
    animator.setRepeatMode(ValueAnimator.REVERSE);
    animator.setInterpolator(new LinearInterpolator());
    animator.addUpdateListener(new ValueAnimator.AnimatorUpdateListener() {
    @Override
    public void onAnimationUpdate(ValueAnimator animation) {
    mPoint = (Point)animation.getAnimatedValue();
    invalidate();
    }
    });
    animator.start();
    }
  • TimeInterpolator 表示动画的速率
    已经有很多已知的实现类,比如:
    AccelerateDecelerateInterpolator 表示先加速后减速(不设置 setInterpolator 默认使用它)
    AccelerateInterpolator 表示一直加速
    DecelerateInterpolator 表示一直加速等
    BounceInterpolator 可以模拟物理规律,实现反弹的效果
    LinearInterpolator 表示按一定的比率持续变化(线性变化的内插器)
  • AnimatorSet 表示动画的集合,可几个动画一起播放,或依次序播放。提供 paly、with、after 等方法。(执行动画顺序 after>play; play>before; play>with)
    例子:
    属性动画-几个动画一起播放
    完整代码:
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    25
    26
    27
    28
    29
    30
    31
    32
    33
    34
    35
    36
    37
    38
    39
    40
    41
    42
    43
    44
    45
    46
    47
    48
    49
    50
    51
    52
    53
    54
    55
    56
    57
    58
    59
    60
    61
    62
    63
    64
    65
    66
    67
    68
    69
    70
    71
    72
    73
    74
    75
    76
    77
    78
    79
    80
    81
    82
    public class MyView extends View {

    private Paint mPaint;
    private Point mPoint;
    private int mColor;

    public MyView(Context context, AttributeSet attrs) {
    super(context, attrs);
    initPaint();
    }

    public MyView(Context context) {
    super(context);
    initPaint();
    }

    private void initPaint() {
    mPaint = new Paint();
    mPaint.setColor(0xFFF00000);
    mPaint.setAntiAlias(true); // 抗锯齿
    }

    @Override
    protected void onDraw(Canvas canvas) {
    super.onDraw(canvas);
    canvas.drawCircle(mPoint.x, mPoint.y, 60, mPaint);
    }

    public void start() {
    final ValueAnimator animator = ValueAnimator.ofObject(new PointEvaluator(),
    new Point(60, 60), new Point(990, 1050));
    animator.setRepeatCount(ValueAnimator.INFINITE);
    animator.addUpdateListener(new ValueAnimator.AnimatorUpdateListener() {
    @Override
    public void onAnimationUpdate(ValueAnimator animation) {
    mPoint = (Point) animation.getAnimatedValue();
    invalidate();
    }
    });

    final ValueAnimator animator1 = ValueAnimator.ofArgb(0xFFF00000,0xFFFFFF00);
    animator1.setRepeatCount(ValueAnimator.INFINITE);
    animator1.addUpdateListener(new ValueAnimator.AnimatorUpdateListener() {
    @Override
    public void onAnimationUpdate(ValueAnimator animation) {
    mColor = (int) animation.getAnimatedValue();
    mPaint.setColor(mColor);
    }
    });

    AnimatorSet animationSet = new AnimatorSet();
    animationSet.setDuration(3000);
    animationSet.setInterpolator(new LgDecelerateInterpolator());

    animationSet.play(animator).with(animator1);
    animationSet.start();
    }

    class PointEvaluator implements TypeEvaluator {
    @Override
    public Object evaluate(float fraction, Object startValue, Object endValue) {
    Point startPoint = (Point) startValue;
    Point endPoint = (Point) endValue;

    int x = (int) (startPoint.x + fraction * (endPoint.x - startPoint.x));
    int y = (int) (startPoint.y + fraction * (endPoint.y - startPoint.y));
    return new Point(x, y);
    }
    }

    class LgDecelerateInterpolator implements TimeInterpolator {
    private float background;
    public LgDecelerateInterpolator() {
    background = 10;
    }

    @Override
    public float getInterpolation(float input) {
    return (1 - (float) Math.pow(background, -input));
    }
    }
    }

TypedArray自定义属性

  1. res/values下新建attrs.xml文件(也可以别的名字)。

  2. attrs.xml内容(编写我们需要的属性):

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    <?xml version="1.0" encoding="utf-8"?>
    <resources>
    <declare-styleable name="burce">
    <attr name="mHeight" format="integer"/>
    <attr name="mWidth" format="integer"/>
    <attr name="mName" format="string"/>
    <attr name="sex" format="enum">
    <enum name="man" value="0"/>
    <enum name="woman" value="1"/>
    </attr>
    <attr name="student" format="boolean"/>
    </declare-styleable>
    </resources>
    • <declare-styleable name="burce">其中的name的值随便定义一个,不要与系统的起冲突。

    • <attr name="mHeight" format="integer"/>name就是自定义的属性的名字(比如系统控件的android:layout_width) format 就是属性的类型,这里支持10种类型,常用的有string,integer,boolean等等,这次我们用到了整形,枚举和布尔

      format支持:

      • color:颜色值;
      • boolean:布尔值;
      • dimension:尺寸值,注意,这里如果是dp那就会做像素转换;
      • float:浮点值;
      • integer:整型值;
      • string:字符串;
      • fraction:百分数;
      • enum:枚举值;
      • flag:是自己定义的,就是里面对应了自己的属性值;
      • reference:指向其它资源;
      • reference|color:颜色的资源文件;
      • reference|boolean:布尔值的资源文件.
    • attr的获取不能使用switch-case,要用if...else

    • 注意:我们在自定义属性的名字的时候不能与系统的名字冲突,否则会报错

  3. 新建一个类继承View类,实现3个构造方法,然后获取我们自定义的属性

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    25
    26
    27
    28
    29
    30
    31
    32
    33
    public class MyView extends View {
    private static final String TAG = "MyView";
    private int heiget;
    private int width;
    private String name;
    private int sex;
    private boolean student;
    public MyView(Context context) {
    this(context,null);
    }

    public MyView(Context context, AttributeSet attrs) {
    this(context, attrs,0);
    }

    public MyView(Context context, AttributeSet attrs, int defStyleAttr) {
    super(context, attrs, defStyleAttr);
    //获取declare-styleable标签。其中burce是自定义标签内的name
    TypedArray array=context.obtainStyledAttributes(attrs, R.styleable.burce);
    heiget=array.getInt(R.styleable.burce_mHeight,0);
    width=array.getInt(R.styleable.burce_mWidth,0);
    name=array.getString(R.styleable.burce_mName);
    sex=array.getInt(R.styleable.burce_sex,0);
    student=array.getBoolean(R.styleable.burce_student,true);
    array.recycle();

    Log.i(TAG, "height: "+heiget);
    Log.i(TAG, "width: "+width);
    Log.i(TAG, "name: "+name);
    Log.i(TAG, "sex: "+sex);
    Log.i(TAG, "student: "+student);
    }
    }
  4. 在layout中使用

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    <?xml version="1.0" encoding="utf-8"?>
    <RelativeLayout 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"
    tools:context="com.myviewtest.MainActivity">
    <com.myviewtest.MyView
    android:layout_width="match_parent"
    android:layout_height="match_parent"
    app:mName="bruce"
    app:sex="man"
    app:mHeight="100"
    app:mWidth="100"
    app:student="true"/>
    </RelativeLayout>
    • 注意:命名空间需要加上xmlns:app="http://schemas.android.com/apk/res-auto"才能使用自定义属性
    • 使用自定义属性需要用app:为前缀
    • 其中命名空间的名称app可以随意指定的,比如改成xmlns:dictview="http://schemas.android.com/apk/res-auto"。那么底下使用的地方就是dictview:mHeight="100"

GcsSloop关于自定义View的一系列文章

自定义View基础-坐标系

View的坐标系

注意:View的坐标系统是相对于父控件而言的.

1
2
3
4
getTop();       //获取子View左上角距父View顶部的距离
getLeft(); //获取子View左上角距父View左侧的距离
getBottom(); //获取子View右下角距父View顶部的距离
getRight(); //获取子View右下角距父View左侧的距离

自定义View坐标系.jpg

MotionEvent中 get 和 getRaw 的区别

1
2
3
4
5
event.getX();       //触摸点相对于其所在组件坐标系的坐标
event.getY();

event.getRawX(); //触摸点相对于屏幕默认坐标系的坐标
event.getRawY();

自定义View中get和getRaw的区别.jpg

View坐标系.png

自定义View基础-角度弧度

自定义View基础-颜色

安卓支持的颜色模式:

颜色模式 备注
ARGB8888 四通道高精度(32位)
ARGB4444 四通道低精度(16位)
RGB565 屏幕默认模式(16位)
Alpha8 仅有透明通道(8位)

其中 A R G B 的取值范围均为0255(即16进制的0x000xff)

A 从0x00到0xff表示从透明到不透明。

RGB 从0x00到0xff表示颜色从浅到深。

当RGB全取最小值(0或0x000000)时颜色为黑色,全取最大值(255或0xffffff)时颜色为白色

颜色混合模式(Alpha通道相关)

默认情况下,当一个颜色绘制到Canvas上时的混合模式是这样计算的:

(RGB通道) 最终颜色 = 绘制的颜色 + (1 - 绘制颜色的透明度) × Canvas上的原有颜色。

注意:

1.这里我们一般把每个通道的取值从0(0x00)到255(0xff)映射到0到1的浮点数表示。

2.这里等式右边的“绘制的颜色”、“Canvas上的原有颜色” 都是经过预乘了自己的Alpha通道的值。如绘制颜色:0x88ffffff,那么参与运算时的每个颜色通道的值不是1.0,而是(1.0 * 0.5333 = 0.5333)。 (其中0.5333 = 0x88/0xff)

使用这种方式的混合,就会造成后绘制的内容以半透明的方式叠在上面的视觉效果。

其实还可以有不同的混合模式供我们选择,用Paint.setXfermode,指定不同的PorterDuff.Mode。

下表是各个PorterDuff模式的混合计算公式:(D指原本在Canvas上的内容dst,S指绘制输入的内容src,a指alpha通道,c指RGB各个通道)

混合模式 计算公式
ADD Saturate(S + D)
CLEAR [0, 0]
DARKEN [Sa + Da - SaDa, Sc(1 - Da) + Dc*(1 - Sa) + min(Sc, Dc)]
DST [Da, Dc]
DST_ATOP [Sa, Sa * Dc + Sc * (1 - Da)]
DST_IN [Sa * Da, Sa * Dc]
DST_OUT [Da * (1 - Sa), Dc * (1 - Sa)]
DST_OVER [Sa + (1 - Sa)*Da, Rc = Dc + (1 - Da)*Sc]
LIGHTEN [Sa + Da - SaDa, Sc(1 - Da) + Dc*(1 - Sa) + max(Sc, Dc)]
MULTIPLY [Sa * Da, Sc * Dc]
SCREEN [Sa + Da - Sa * Da, Sc + Dc - Sc * Dc]
SRC [Sa, Sc]
SRC_ATOP [Da, Sc * Da + (1 - Sa) * Dc]
SRC_IN [Sa * Da, Sc * Da]
SRC_OUT [Sa * (1 - Da), Sc * (1 - Da)]
SRC_OVER [Sa + (1 - Sa)*Da, Rc = Sc + (1 - Sa)*Dc]
XOR [Sa + Da - 2 * Sa * Da, Sc * (1 - Da) + (1 - Sa) * Dc]

用示例图来查看使用不同模式时的混合效果如下(src表示输入的图,dst表示原Canvas上的内容):自定义View颜色混合模式效果图.jpg

自定义View进阶-分类与流程

自定义View绘制流程函数调用链(简化版).jpg

代码:

复杂布局进行模块拆分

作用:模块顺序是固定的。拆分模块,各模块处理自己的逻辑避免全部逻辑都混在Activity中造成代码复杂难以维护。

难点:Activity与各模块间的交互相对较复杂

本质:自定义View

代码

ProDetailPriceUI.java 商品详情价格区域相关UI

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
package xxx.xxx.view.productDetail.ui;
...

/**
* 商品详情价格区域相关UI
*/
public class ProDetailPriceUI extends FrameLayout {


@Bind(R.id.tv_level_name)
TextView tvLevelName;
...

TextView tvSaleGroupNum;
private Context context;
private GoodsTagModelWork goodsTagModelWork;
private ProDetailBean mBean;
private String storeId;

public ProDetailPriceUI(Context context) {
this(context, null);
}

public ProDetailPriceUI(Context context, AttributeSet attrs) {
this(context, attrs, 0);
}

public ProDetailPriceUI(Context context, AttributeSet attrs, int defStyleAttr) {
super(context, attrs, defStyleAttr);
this.context = context;
inflate(context, R.layout.pro_detail_price_view, this);
ButterKnife.bind(this, this);
//刚开始初始化则隐藏这个布局
setVisibility(GONE);
}


/**
* 设置价格相关数据展示
*/
public void setUIData(ProDetailBean bean, String storeId) {
this.storeId = storeId;
if (bean == null) {
this.setVisibility(GONE);
return;
}
//设置数据的时候说明有相关数据,此时再展示这个布局
setVisibility(VISIBLE);
mBean = bean;

//设置各个控件的显隐和值
...
}

@OnClick({R.id.ll_super_member, R.id.tv_svip_label})
public void onViewClick(View view) {
switch (view.getId()) {
case R.id.tv_svip_label:
case R.id.ll_super_member:
UIHelper.goMemberArea(context, storeId);
break;
}
}
}

如果自定义UI跟Activity有交互,那么要传递callback到自定义UI中,在自定义UI中合理位置设置回调

ProDetailActivity.java

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
411
412
413
414
415
416
417
418
419
420
421
422
423
424
425
426
427
428
429
430
431
432
433
434
435
436
437
438
439
440
441
442
443
444
445
446
447
448
449
450
451
452
453
454
455
456
457
458
459
460
461
462
463
464
465
466
467
468
469
470
471
472
473
474
475
476
477
478
479
480
481
482
483
484
485
486
487
488
489
490
491
492
493
494
495
496
497
498
499
500
501
502
503
504
505
506
507
508
509
510
511
512
513
514
515
516
517
518
519
520
521
522
523
524
525
526
527
528
529
530
531
532
533
534
535
536
537
538
539
540
541
542
543
544
545
546
547
548
549
550
551
552
553
554
555
556
557
558
559
560
561
562
563
564
565
566
567
568
569
570
571
572
573
574
575
576
577
578
579
580
581
582
583
584
585
586
587
588
589
590
591
592
593
594
595
596
597
598
599
600
601
602
603
604
605
606
607
608
609
610
611
612
613
614
615
616
617
618
619
620
621
622
623
624
625
626
627
628
629
630
631
632
633
634
635
636
637
638
639
640
641
642
643
644
645
646
647
648
649
650
651
652
653
654
655
656
657
658
659
660
661
662
663
664
665
666
667
668
669
670
671
672
673
674
675
676
677
678
679
680
681
682
683
684
685
686
687
688
689
690
691
692
693
694
695
696
697
698
699
700
701
702
703
704
705
706
707
708
709
710
711
712
713
714
715
716
717
718
719
720
721
722
723
724
725
726
727
728
729
730
731
732
733
734
735
736
737
738
739
740
741
742
743
744
745
746
747
748
749
750
751
752
753
754
755
756
757
758
759
760
761
762
763
764
765
766
767
768
769
770
771
772
773
774
775
776
777
778
779
780
781
782
783
784
785
786
787
788
789
790
791
792
793
794
795
796
797
798
799
800
801
802
803
804
805
806
807
808
809
810
811
812
813
814
815
816
817
818
819
820
821
822
823
824
825
826
827
828
829
830
831
832
833
834
835
836
837
838
839
840
841
842
843
844
845
846
847
848
849
850
851
852
853
854
855
856
857
858
859
860
861
862
863
864
865
866
867
868
869
870
871
872
873
874
875
876
877
878
879
880
881
882
883
884
885
886
887
888
889
890
891
892
893
894
895
896
897
898
899
900
901
902
903
904
905
906
907
908
909
910
911
912
913
914
915
916
917
918
919
920
921
922
923
924
925
926
927
928
929
930
931
932
933
934
935
936
937
938
939
940
941
942
943
944
945
946
947
948
949
950
951
952
953
954
955
956
957
958
959
960
961
962
963
964
965
966
967
968
969
970
971
972
973
974
975
976
977
978
979
980
981
982
983
984
985
986
987
988
989
990
991
992
993
994
995
996
997
998
999
1000
1001
1002
1003
1004
1005
1006
1007
1008
1009
1010
1011
1012
1013
1014
1015
1016
1017
1018
1019
1020
1021
1022
1023
1024
1025
1026
1027
1028
1029
1030
1031
1032
1033
1034
1035
1036
1037
1038
1039
1040
1041
1042
1043
1044
1045
1046
1047
1048
1049
1050
1051
1052
1053
1054
1055
1056
1057
1058
1059
1060
1061
1062
1063
1064
1065
1066
1067
1068
1069
1070
1071
1072
1073
1074
1075
1076
1077
1078
1079
1080
1081
1082
1083
1084
1085
1086
1087
1088
1089
1090
1091
1092
1093
1094
1095
1096
1097
1098
1099
1100
1101
1102
1103
1104
1105
1106
1107
1108
1109
1110
1111
1112
1113
1114
1115
1116
1117
1118
1119
1120
1121
1122
1123
1124
1125
1126
1127
1128
1129
1130
1131
1132
1133
1134
1135
1136
1137
1138
1139
1140
1141
1142
1143
1144
1145
1146
1147
1148
1149
1150
1151
1152
1153
1154
1155
1156
1157
1158
1159
1160
1161
1162
1163
1164
1165
1166
1167
1168
1169
1170
1171
1172
1173
1174
1175
1176
1177
1178
1179
1180
1181
1182
1183
1184
1185
1186
1187
1188
1189
1190
1191
1192
1193
1194
1195
1196
1197
1198
1199
1200
1201
1202
1203
1204
1205
1206
1207
1208
1209
1210
1211
1212
1213
1214
1215
1216
1217
1218
1219
1220
1221
1222
1223
1224
1225
1226
1227
1228
1229
1230
1231
1232
1233
1234
1235
1236
1237
1238
1239
1240
1241
1242
1243
1244
1245
1246
1247
1248
1249
1250
1251
1252
1253
1254
1255
1256
1257
1258
1259
1260
1261
1262
1263
1264
1265
1266
1267
1268
1269
1270
1271
1272
1273
1274
1275
1276
1277
1278
1279
1280
1281
1282
1283
1284
1285
1286
1287
1288
1289
1290
1291
1292
1293
1294
1295
1296
1297
1298
1299
1300
1301
1302
1303
1304
1305
1306
1307
1308
1309
1310
1311
1312
1313
1314
1315
1316
1317
1318
1319
1320
1321
1322
1323
1324
1325
1326
1327
1328
1329
1330
1331
1332
1333
1334
1335
1336
1337
1338
1339
1340
1341
1342
1343
1344
1345
1346
1347
1348
1349
1350
1351
1352
1353
1354
1355
1356
1357
1358
1359
1360
1361
1362
1363
1364
1365
1366
1367
1368
1369
1370
1371
1372
1373
1374
1375
1376
1377
1378
1379
1380
1381
1382
1383
1384
1385
1386
1387
1388
1389
1390
1391
1392
1393
1394
1395
1396
1397
1398
1399
1400
1401
1402
1403
1404
1405
1406
1407
1408
1409
1410
1411
1412
1413
1414
1415
1416
1417
1418
1419
1420
1421
1422
1423
1424
1425
1426
1427
1428
1429
1430
1431
1432
1433
1434
1435
1436
1437
1438
1439
1440
1441
1442
1443
1444
1445
1446
1447
1448
1449
1450
1451
1452
1453
1454
1455
1456
1457
1458
1459
1460
1461
1462
1463
1464
1465
1466
1467
1468
1469
1470
1471
1472
1473
1474
1475
1476
1477
1478
1479
1480
1481
1482
1483
1484
1485
1486
1487
1488
1489
1490
1491
1492
1493
1494
1495
1496
1497
1498
1499
1500
1501
1502
1503
1504
1505
1506
1507
1508
1509
1510
1511
1512
1513
1514
1515
1516
1517
1518
1519
1520
1521
1522
1523
1524
1525
1526
1527
1528
1529
1530
1531
1532
1533
1534
1535
1536
1537
1538
1539
1540
1541
1542
1543
1544
1545
1546
1547
1548
1549
1550
1551
1552
1553
1554
1555
...
import butterknife.Bind;
import butterknife.ButterKnife;
import butterknife.OnClick;
...

/**
* 新商品详情
*/
public class ProDetailActivity
extends LdyBaseActivity
implements NewProdetailSkuDialog.SkuOperationListener, ProductPresenter.PackageListListener {

@Bind(R.id.pro_detail_price_view)
ProDetailPriceUI proDetailPriceView;
...

private ProductPresenter productPresenter;
private ProDetailBean proDetailBean;
private ProSkuInfoBean proSkuInfoBean;
...

@IntDef({TOP_SHARE,INVITE_SHARE})
@interface shareWay{

}
@shareWay
private int mCurShareWay;

@Override
protected int setLayoutResId() {
return R.layout.activity_pro_detail_new;
}

@Override
protected void onCreate() {
initView();
initData();
}

@Override
protected void onResume() {
super.onResume();
getUnreadCountWithTargetId();
}

private void getUnreadCountWithTargetId(){
if (!UnifiedCustomerService.isUseCustomerServiceByDefault()) {
//region add by shenbh on 2019/8/21 添加导购id
int serviceGuideId = Constants.cust.getGuiderId();
if (proDetailBean != null && proDetailBean.getServiceGuide() != 0){
serviceGuideId = proDetailBean.getServiceGuide();
}
//endregion add
RongMsgManager.getInstance().getUnreadCountWithTargetId(Conversation.ConversationType.PRIVATE, "dg_" + serviceGuideId, new IUnReadMsgCountListener() {
@Override
public void getUnReadMsgCount(int count) {
if (productDetailImNoticeTv != null) {
productDetailImNoticeTv.setGuiderCount(count, false);
}
}
});
}
}

@Override
public void setImmersion() {
getImmersion().setImmersionDarkFont(rlTitle, true);
}

public void initView() {
EventBus.getDefault().register(this);
ButterKnife.bind(this);
initTitle();
dealWithIntentParams();
setListener();
initShareView();
// 初始化客服服务View
// if (UnifiedCustomerService.isUseUnionServiceOnly() || UnifiedCustomerService.isUseCustomerServiceByDefault()) {
// guiderAliasTv.setText("客服");
// } else {
// guiderAliasTv.setText(StringUtils.isEmpty(SysHelper.getGuiderAlias(this))?"导购":SysHelper.getGuiderAlias(this));
// }
//初始化客服服务View
if (UnifiedCustomerService.isUseCustomerServiceByDefault()) {
guiderAliasTv.setText("客服");
} else {
guiderAliasTv.setText(StringUtils.isEmpty(SysHelper.getGuiderAlias(this)) ? "导购" : SysHelper.getGuiderAlias(this));
}

//初始化错误信息View
if (NetUtil.isNetworkConnected(this)) {
showErrorInfo(false, null);
} else {
showErrorInfo(true, "当前网络连接不可用");
}
//初始化SKU弹窗Dialog
skuDialog = new NewProdetailSkuDialog(this);
skuDialog.setSkuOperationListener(this);
//初始化广告轮播图
ViewGroup.LayoutParams lp = proDetailBannerRl.getLayoutParams();
lp.height = DimensUtil.getDisplayWidth(this);
proDetailBannerRl.setLayoutParams(lp);
displayHeight = DimensUtil.getDisplayWidth(this);
}

/**
* 设置已下架商品显示的样式
*/
private void setOffShelfView(){
//隐藏配送方式
proDetailDeliveryModeView.setVisibility(View.GONE);
//隐藏拼团信息
proDetailGroupInfoView.setVisibility(View.GONE);
//隐藏券详情
proDetailCouponView.setVisibility(View.GONE);
//促销
proDetailPromotionInfoView.setVisibility(View.GONE);
//服务说明
proDetailServiceTipView.setVisibility(View.GONE);
//商品套餐
mealView.setVisibility(View.GONE);
//品牌商/供应商信息展示
proDetailBrandInfoView.setVisibility(View.GONE);
//直播信息
proDetailLiveInfoView.setVisibility(View.GONE);
//评价信息
proDetailEvaluateInfoView.setVisibility(View.GONE);
//店铺信息
proDetailShopInfoView.setVisibility(View.GONE);
//店铺信息
proDetailShopInfoView2.setVisibility(View.GONE);
//H5
proDetailWebView.setVisibility(View.GONE);
}

private void initTitle() {
tvTitle.setText("商品详情");
setImmersion();
setExpandedState();
llProDetail.setPadding(0,BarUtils.getStatusBarHeight()+BarUtils.getActionBarHeight(this),0,0);
}

/**
* 处理传递过来的参数
*/
private void dealWithIntentParams() {
Intent intent = getIntent();
if (intent == null) {
return;
}

isFromIntegralExchange = intent.getBooleanExtra("isFromIntegralExchange", false);
couponId = intent.getStringExtra("couponId");

if (App.getContext().getAppIndexingManager().isAppIndexing(intent)) {
//H5唤起的情况
Constants.getCustomer();
storeId = Constants.cust.getStoreId();
proId = intent.getStringExtra(AppIndexingCenter.EXTRA_ITEM_ID);
proType = "1";
liveId = intent.getStringExtra(UIHelper.EXTRA_LIVE_ID);
liveType = intent.getStringExtra(UIHelper.EXTRA_LIVE_TYPE);
stockType = "0";
packageId = "0";
cityDelivery = (SpeedinessBean.CityDelivery) intent.getSerializableExtra(StringConstantUtils.EXTRA_CITY_DELIVERY);
} else {
//正常跳转
storeId = intent.getStringExtra("storeId");
if (StringUtils.isEmpty(storeId)|| TextUtils.equals("0",storeId)) {
storeId = Constants.cust.getStoreId();
}
proId = StringUtils.isEmpty(intent.getStringExtra("itemId")) ? "0" : intent.getStringExtra("itemId");
proType = intent.getStringExtra("ItemType");
if (StringUtils.isEmpty(proType)) {
proType = "1";
}
liveId = intent.getStringExtra(UIHelper.EXTRA_LIVE_ID);
liveType = intent.getStringExtra(UIHelper.EXTRA_LIVE_TYPE);
stockType = intent.getStringExtra("StockType");
if (StringUtils.isEmpty(stockType)) {
stockType = "0";
}

packageId = intent.getStringExtra(UIHelper.EXTRA_PACKAGE_ID);
if (StringUtils.isEmpty(packageId)) {
packageId = "0";
}
regionCode = intent.getStringExtra(StringConstantUtils.EXTRA_REGION_CODE);
cityDelivery = (SpeedinessBean.CityDelivery) intent.getSerializableExtra(StringConstantUtils.EXTRA_CITY_DELIVERY);
}
proDetailPromotionInfoView.setStockType(stockType);
proDetailDeliveryModeView.setCityDelivery(cityDelivery);
}

/**
* 初始化分享view
*/
private void initShareView() {
ivShare.setVisibility(View.VISIBLE);
if (SysHelper.getShareBusinessConfig().isOpenShareItemSendIntegral()) {
ivShare.setBackgroundResource(R.drawable.ic_share_detail_new);
} else {
ivShare.setBackgroundResource(R.drawable.ic_share02_detail_new);
}
}

/**
* 头部监听、轮播监听、继续拖动查看商品详情的监听
*/
private void setListener() {
rlTitle.setNavigationOnClickListener(new View.OnClickListener() {
@Override
public void onClick(View v) {
onBackPressed();
}
});
//继续拖动查看商品详情的监听
sdlPro.setOnSlideDetailsListener(new SlideDetailsLayout.OnSlideDetailsListener() {
@Override
public void onStatucChanged(SlideDetailsLayout.Status status) {
if (mVideoLayout != null) {
if (status == SlideDetailsLayout.Status.OPEN && mVideoLayout.getPlayerState() == PlayerState.PLAYING
&& mFloatVideoRlyt.getVisibility() == View.GONE) {//商品已拖动显示商品详情scrollView的滑动监听还未至主图被覆盖
isSlideShow = true;
showFloatVideo();
} else if (isSlideShow && mFloatVideoRlyt.getVisibility() == View.VISIBLE) {
hideFloatVideo();
}
}
}

@Override
public void onScrollChanged(int scrollY) {
if (scrollY >= 30) {
isCollapsedState = true;
setCollapsedState();
} else {
isCollapsedState = false;
setExpandedState();
}
//-------视频相关start--------
//主图在播放视频条件下,判断主图区域是否被覆盖
if (mVideoLayout != null) {
Log.e("video", "displayHeight"+displayHeight);
Log.e("scrollY", "scrollY"+scrollY);
if (scrollY >= displayHeight - DensityUtil.dp2px(56) - DimensUtil.getStatusHeight(ProDetailActivity.this)
&& mVideoLayout.getPlayerState() == PlayerState.PLAYING
&& mFloatVideoRlyt.getVisibility() == View.GONE) {//主图被覆盖,且主图在播放
showFloatVideo();
} else if (scrollY <= displayHeight - DensityUtil.dp2px(56) - DimensUtil.getStatusHeight(ProDetailActivity.this)
&& mFloatVideoRlyt.getVisibility() == View.VISIBLE) {//主图不再覆盖,且悬浮视频显示
hideFloatVideo();
}
}
}
});
proDetailImgVp.addOnPageChangeListener(new ViewPager.OnPageChangeListener() {
@Override
public void onPageScrolled(int position, float positionOffset, int positionOffsetPixels) {
}

@Override
public void onPageSelected(int position) {
if (StringUtils.notBank(proDetailBean.getVideoUrl())) {
bannerVideoAdapter.setCurrentposition(position);
//视频播放按钮
if (position == 0) {
//主图视频为暂停状态,则图片滑动回视频位置时继续播放当前进度
if (mVideoLayout != null && mVideoLayout.getPlayerState() == PlayerState.PAUSED) {
mVideoLayout.restartCurVideoView();
}
//如主图视频已经显示,不再显示页标、倒计时
if (mVideoLayout != null && mVideoLayout.getPlayerState() != PlayerState.IDLE) {
proImgPageTv.setVisibility(View.GONE);
proDetailVideoView.setVisibility(View.GONE);
tvGroupTime.setVisibility(View.GONE);
} else {
proImgPageTv.setText((position + 1) + "/" + bannerVideoAdapter.getCount());
proImgPageTv.setVisibility(View.VISIBLE);
proDetailVideoView.setVisibility(View.VISIBLE);
//拼团倒计时
if (!StringUtils.isEmpty(proDetailBean.getGroupActivityId()) &&
StringUtils.isEmpty(proDetailBean.getMemberPriceLabel())) {
tvGroupTime.setVisibility(View.VISIBLE);
}
}
} else {
proDetailVideoView.setVisibility(View.GONE);
//主图视频为播放状态,则暂停
if (mVideoLayout != null && mVideoLayout.getPlayerState() == PlayerState.PLAYING) {
mVideoLayout.pauseCurVideoView();
}
//正常显示页标和倒计时
proImgPageTv.setText((position + 1) + "/" + bannerVideoAdapter.getCount());
proImgPageTv.setVisibility(View.VISIBLE);
//拼团倒计时
if (!StringUtils.isEmpty(proDetailBean.getGroupActivityId()) &&
StringUtils.isEmpty(proDetailBean.getMemberPriceLabel())) {
tvGroupTime.setVisibility(View.VISIBLE);
}
}
} else {
proImgPageTv.setText((position + 1) + "/" + bannerAdapter.getCount());
}
}

@Override
public void onPageScrollStateChanged(int state) {

}
});
//-------视频相关end--------
}

/**
* 头部展开状态
*/
private void setExpandedState() {
rlTitle.setBackgroundColor(
ColorUtils.blendARGB(
Color.TRANSPARENT,
ContextCompat.getColor(
this,
R.color.u1city_frame_toolbar_bg_color),
0));

tvTitle.setAlpha(0f);
}

/**
* 头部收缩状态
*/
private void setCollapsedState() {
rlTitle.setBackgroundColor(
ColorUtils.blendARGB(
Color.TRANSPARENT,
ContextCompat.getColor(
this,
R.color.u1city_frame_toolbar_bg_color),
1));
tvTitle.setAlpha(1f);
}


public void initData() {
//初始化Presenter
productPresenter = new ProductPresenter(this);
proSkuPresenter = new ProSkuPresenter(this);
productPresenter.setPackageListListener(this);
//获取商品默认的配送区域
if (BaseParser.parseInt(stockType) != 1 && BaseParser.parseInt(stockType) != 2) {
List<NextDayAddressBean> proSkuInfoList = DataSupport.where("proId = ?", proId).find(NextDayAddressBean.class);
if (!ListUtils.isEmpty(proSkuInfoList)) {
regionCode = proSkuInfoList.get(0).getRegionCode();
}
}
//获取商品数据
getProDetailData();
//加载商品信息
if (NetUtil.isNetworkConnected(this)) {
loadDetailData();
getSkuInfo();
}
}

/**
* 加载商品详情数据(详情、直播、评价、卡券列表)
*/
private void loadDetailData() {
if (productPresenter != null){
productPresenter.getUpdateItemDetail(Constants.getCustomerId() + "", storeId,
proId, App.getContext().customerLng, App.getContext().customerLat,
stockType, StringUtils.isEmpty(regionCode) ? "" : regionCode, "", couponId);
}
}

/**
* 获取商品详情数据及操作商品数据回调
*/
private void getProDetailData() {
productPresenter.setDetailContract(new ProductDetailContract() {
@Override
public void getProductDetailBean(ProDetailBean proDetailBean) {
getUnreadCountWithTargetId();
showProDetailData(proDetailBean, 0);
setShopCarNum();
getModularData();
initShareGetIntegralTipsPopupWindow();
}

@Override
public void itemLiveInfo(List<LiveInfoBean> liveInfoBean) {
if (proDetailLiveInfoView != null) {
proDetailLiveInfoView.setLiveInfo(liveId, liveInfoBean);
}
}


@Override
public void itemEvaluationInfo(ProEvaluationInfoBean proEvaluationInfoBean) {
if (proEvaluationInfoBean != null && proDetailEvaluateInfoView != null) {
proDetailEvaluateInfoView.setEvaluateData(proEvaluationInfoBean, proId);
}
}

@Override
public void itemCouponList(List<CashCouponBean> availableCouponList, List<CashCouponBean> inAvailableCouponList) {
if (proDetailCouponView != null && proDetailPromotionInfoView != null) {
proDetailCouponView.setStoreId(storeId);
proDetailCouponView.setCouponInfoData(proDetailBean, availableCouponList, inAvailableCouponList);
proDetailPromotionInfoView.setPadding(0,ListUtils.isEmpty(availableCouponList)?DimensUtil.dpToPixels(ProDetailActivity.this,10):0,0,0);
}
}

@Override
public void itemPromotionTagInfo(PromotionTagListBean promotionTagListBean) {
if (proDetailPromotionInfoView != null && proDetailServiceTipView != null) {
//region add by shenbh on 2019/8/12 平台页改造,要带上商品的storeId
for (PromotionTagListBean.PromotionTagInfoBean promotionTagInfoBean:
promotionTagListBean.getPromotionTagList()) {
promotionTagInfoBean.setStoreId(storeId);
}
//endregion add
proDetailPromotionInfoView.setPromotionInfoData(proDetailBean, promotionTagListBean);
proDetailServiceTipView.setPadding(0,ListUtils.isEmpty(promotionTagListBean.getPromotionTagList())?DimensUtil.dpToPixels(ProDetailActivity.this,10):0,0,0);
}
}


@Override
public void joinGroupImmediately(int code, ContentValues values) {
dealGroupBuyData(code, values);
}

@Override
public void error(BaseAnalysis analysis) {
switch (analysis.getStatus()) {
case "002":
proDetailBean = JsonAnalysis.getInstance().fromJson(analysis.getResult(), ProDetailBean.class);
showProDetailData(proDetailBean, 1);
setShopCarNum();
//region modify by shenbh on 2019/8/15 与ios一致,已下架商品就不调用getModularData()方法
// getModularData();
//endregion modify
initShareGetIntegralTipsPopupWindow();
// IMHelper.getInstance().getUnreadMsgCount();
break;
case "003":
showErrorInfo(true, analysis.msg());
break;
case "006":
showErrorInfo(true, analysis.msg());
break;
case "009":
ImageView emptyView = (ImageView) proEmptyLl.getChildAt(0);
emptyView.setImageResource(R.drawable.emty_image_message);
showErrorInfo(true, "~获取商品拼团信息失败~");
break;
default:
// showErrorInfo(true, "获取商品信息错误");
showErrorInfo(true, analysis.msg());
break;
}

//add by shenbh on 2019/4/18 来自积分兑换,更新界面
if (isFromIntegralExchange){
refreshViews();
}
}
});


//收藏操作回调
productPresenter.setCollectCallbackListener(new ProductPresenter.CollectCallbackListener() {
@Override
public void requestSuccess() {
if (hasCollect == 0) {
changeCollectState(1);
ToastUtil.showToast(ProDetailActivity.this,"成功收藏该商品~");
} else {
changeCollectState(0);
ToastUtil.showToast(ProDetailActivity.this,"取消收藏该商品~");
}
}
});


}

/**
* 获取模块信息(包括套餐、直播、促销、评价、领券)
*/
private void getModularData() {
if (proDetailBean.getIsShowPackageItem() == 1) {
productPresenter.getPackageItemInfo(storeId, proId);
}
// if (!StringUtils.isEmpty(liveId)) {
productPresenter.getItemLiveInfo(Constants.getCustomerId(), storeId, proId);
// }
productPresenter.getItemPromotionTagInfo(proId, storeId);
productPresenter.getItemEvaluationInfo(Constants.getCustomerId(), storeId, proId);
productPresenter.getItemCouponList(Constants.getCustomerId() + "", storeId, proId);
}

/**
* 展示商品详情数据
*/
private void showProDetailData(ProDetailBean proDetailBean, int itemType) {
ProDetailActivity.this.proDetailBean = proDetailBean==null?new ProDetailBean():proDetailBean;
if (proDetailBean!=null){
hasCollect = proDetailBean.getHasLike();
detailURL = String.format("%s/pageDetail?itemId=%s&itemType=%s", Constants.getLdyH5Url(), proDetailBean.getLocalItemId(), proDetailBean.getItemType());
}

//价格显示
proDetailPriceView.setUIData(proDetailBean, storeId);
。。。
//加载H5页面
proDetailWebView.loadUrl(detailURL);
//切换商品收藏状态
changeCollectState(hasCollect);
//切换展示底部操作按钮
changeFooterBtn(proDetailBean, itemType);
}

/**
* 展示商品图片信息 - 加视频
*/
private void showBannerPager() {
ShapeUtil.getInstance().setFull(proImgPageTv, 180, R.color.pro_detail_indicator_color);
String[] picsPathList = proDetailBean.getPicsPathList();
banners = new ArrayList<BaseModel>();
BaseModel baseModel = new BaseModel();
baseModel.setPicUrl(proDetailBean.getPicUrl());
banners.add(baseModel); // 商品主图显示在第一张
if (picsPathList != null && picsPathList.length > 0) {
for (int i = 0; i < picsPathList.length; i++) {
if (!StringUtils.isEquals(picsPathList[i], proDetailBean.getPicUrl())) {
baseModel = new BaseModel();
baseModel.setPicUrl(picsPathList[i]);
banners.add(baseModel);
}
}
}
int height = DimensUtil.getDisplayWidth(this);
//--------------------------视频相关 start------------------------
// proDetailBean.setVideoUrl("http://fy635photo.laidy.com.cn/34c92468-c450-4210-a045-e3fde4efe4ba.mp4");
if (StringUtils.notBank(proDetailBean.getVideoUrl())) {
mVideoLayout = new VideoLayout(this);
proDetailVideoView.setVisibility(View.VISIBLE);
// proDetailVideoView.setVideoDuration(proDetailBean.getVideoDuration());
proImgPageTv.setText("1/" + banners.size());
setVideoViewClick();
//点击封面图片也能播放视频
mVideoLayout.setOnCoverImageClickListener(new OnCoverImageClickListener() {
@Override
public void onCoverImageClickListener() {
coverImageClick();
}
});
bannerVideoAdapter = new BannerVideoImageAdapter(this, banners, height,
ImageView.ScaleType.FIT_CENTER, 1, R.drawable.ic_default_pro_bg,
proDetailBean.getVideoUrl(), mVideoLayout);
proDetailImgVp.setAdapter(bannerVideoAdapter);
} else {
proDetailVideoView.setVisibility(View.GONE);
bannerAdapter = new BannerAdapter(this, banners, height,
ImageView.ScaleType.FIT_CENTER, 1, R.drawable.ic_default_pro_bg);
proDetailImgVp.setAdapter(bannerAdapter);
proImgPageTv.setText("1/" + bannerAdapter.getCount());
}
}

/**
* 封面播放按钮点击
*/
private void setVideoViewClick() {
if (proDetailVideoView != null) {
proDetailVideoView.setOnClickListener(new View.OnClickListener() {
@Override
public void onClick(View v) {
coverImageClick();
}
});
}
}

/**
* 封面/封面播放按钮 点击
*/
private void coverImageClick() {
//播放按钮:未播放、暂停状态下点击按钮、视频区域均可播放视频
if (mVideoLayout != null) {
mVideoLayout.startCurVideoView();
proDetailVideoView.setVisibility(View.GONE);
tvGroupTime.setVisibility(View.GONE);
proImgPageTv.setVisibility(View.GONE);
}
}

/**
* 音量增加/减小键
*/
@Override
public boolean onKeyDown(int keyCode, KeyEvent event) {
switch (keyCode) {
case KeyEvent.KEYCODE_VOLUME_UP:
changeVoice();
break;
case KeyEvent.KEYCODE_VOLUME_DOWN:
changeVoice();
break;
default:
break;
}
return super.onKeyDown(keyCode, event);
}

public void changeVoice() {
if (mVideoLayout != null) {
mVideoLayout.changeVoice();
}
}

//---------视频悬浮窗相关-------

/**
* 设置悬浮视频播放器,并开始播放(参考videoLayout.onViewCreated方法初始化)
*
* @param curVideoView 传入外部播放view(主图的PLVideoTextureView)
*/
private void initStartFloatVideo(PLVideoTextureView curVideoView) {
//初始化设置
if (curVideoView != null) {
//传递PLVideoTextureView的时候需要把PLVideoTextureView从原先的布局中移除
mVideoLayout.removeVideoView();//存放PLVideoTextureView的布局,切成悬浮视频时需要removeView
mFloatVideoTextureGroup.removeAllViews();
mFloatPLVideoTextureView = curVideoView;
}
//不设置播放封面、不显示网络情况等
mFloatPLVideoTextureView.setVideoPath(proDetailBean.getVideoUrl());

AVOptionsUtils.setAVOptions(mFloatPLVideoTextureView,
new AVOptionsUtils.Builder()
.setPreferFormat(AVOptions.PREFER_FORMAT_MP4));
mFloatPLVideoTextureView.setMediaController(mFloatPortraitMC);
mFloatPLVideoTextureView.setDisplayAspectRatio(PLVideoTextureView.ASPECT_RATIO_PAVED_PARENT);//全屏铺满
reSizeFloatVideo(mVideoLayout.getPLVideoTextureView().getWidth(), mVideoLayout.getPLVideoTextureView().getHeight());
mFloatVideoTextureGroup.addView(mFloatPLVideoTextureView, -1);
mFloatPortraitMC.setAnchorView(mFloatPLVideoTextureView);
//true = 播放结束后会自动重新开始,false = 播放结束会调用OnCompletionListener
mFloatPLVideoTextureView.setLooping(true);
mFloatVideoRlyt.setVisibility(View.VISIBLE);
//已初始化标志
mIsInitedPLVideo = true;

//悬浮视频点击打开视频播放页面
mFloatVideoTextureGroup.setOnClickListener(new View.OnClickListener() {
@Override
public void onClick(View v) {
goVideoImageActivity();
}
});
}

/**
* 悬浮视频关闭 - 主图视频暂停播放
*/
public void closeFloatVideo() {
mFloatVideoRlyt.setVisibility(View.GONE);
mFloatVideoTextureGroup.removeAllViews();//悬浮视频移除播放器
mVideoLayout.removeVideoView();
mVideoLayout.onPortraitChanged(mFloatPLVideoTextureView);//加入到主图视频布局中
mVideoLayout.pauseCurVideoView();
mFloatVideoRlyt.setVisibility(View.GONE);
resize(mFloatVideoTextureGroup.getWidth(), mFloatVideoTextureGroup.getHeight());
}

/**
* 悬浮视频显示 - 主图视频加入到悬浮视频继续播放
*/
public void showFloatVideo() {
// 主图视频 - 停止播放
if (!mIsInitedPLVideo) {
initStartFloatVideo(mVideoLayout.getPLVideoTextureView());
} else {
mVideoLayout.removeVideoView();//主图视频移除播放器
mFloatVideoTextureGroup.removeAllViews();
reSizeFloatVideo(mVideoLayout.getPLVideoTextureView().getWidth(), mVideoLayout.getPLVideoTextureView().getHeight());
mFloatVideoTextureGroup.addView(mVideoLayout.getPLVideoTextureView(), -1);
mFloatVideoRlyt.setVisibility(View.VISIBLE);
}
}

/**
* 悬浮视频隐藏 - 加回主图视频继续播放
*/
public void hideFloatVideo() {
mFloatVideoRlyt.setVisibility(View.GONE);
mFloatVideoTextureGroup.removeAllViews();//悬浮视频移除播放器
mVideoLayout.removeVideoView();
mVideoLayout.onPortraitChanged(mFloatPLVideoTextureView);//加入到主图视频布局中
mFloatVideoRlyt.setVisibility(View.GONE);
resize(mFloatVideoTextureGroup.getWidth(), mFloatVideoTextureGroup.getHeight());
}

//---------视频悬浮窗相关end-------

/**
* 视频+图片,返回的Event
*
* @param event
*/
@Subscribe(threadMode = ThreadMode.MAIN)
public void onEvent(VideoImageOutEvent event) {
if (event != null) {
//视频只回商品详情页主图,悬浮视频隐藏
if (mFloatVideoRlyt.getVisibility() == View.VISIBLE) {
mFloatVideoRlyt.setVisibility(View.GONE);
}
//type:0 = 视频,1 = 图片
proDetailImgVp.setCurrentItem(event.getPosition());
bannerVideoAdapter.setCurrentposition(event.getPosition());
//拼团倒计时
if (!StringUtils.isEmpty(proDetailBean.getGroupActivityId()) &&
StringUtils.isEmpty(proDetailBean.getMemberPriceLabel())) {
tvGroupTime.setVisibility(View.VISIBLE);
}
if (event.getType() == 0 || event.getPosition() == 0) {//type:0 = 视频,1 = 图片 || 视频页为第一张图片处返回,则返回主图视频
proDetailVideoView.setVisibility(View.VISIBLE);
proImgPageTv.setText("1" + "/" + bannerVideoAdapter.getCount());
proImgPageTv.setVisibility(View.VISIBLE);
}
//重设播放器
PLVideoTextureView plVideoTextureView = event.getPLVideoTextureView();
if (plVideoTextureView != null) {
mVideoLayout.removeVideoView();
mVideoLayout.onPortraitChanged(plVideoTextureView);
//mVideoLayout.restartCurVideoView();暂不做继续播放的需求
mVideoLayout.stopCurVideoView();
}
}
}

/**
* 进入视频播放页面(视频(横竖屏)+图片组合)
*/
public void goVideoImageActivity() {
//传递PLVideoTextureView的时候需要把PLVideoTextureView从原先的布局中移除
mFloatVideoTextureGroup.removeAllViews();//悬浮视频移除播放器
VideoImageEvent videoImageEvent = new VideoImageEvent(
0, mFloatPLVideoTextureView, false, banners,
0, proDetailBean.getVideoUrl(), banners.get(0).getPicUrl(), mFloatVideoTextureGroup.getWidth(),
mFloatVideoTextureGroup.getHeight());
EventBus.getDefault().postSticky(videoImageEvent);
Intent intent = new Intent(this, VideoImageActivity.class);
startActivity(intent);
}

/**
* 重新给视频播放器设置大小
*
* @param width 视频相对宽度
* @param height 视频相对高度
*/
private void reSizeFloatVideo(int width, int height) {
// 重新设置视频的播放大小(与其他处视频不同的是,悬浮视频不补黑边,悬浮视频宽固定,高按比例设置不固定。)
FrameLayout.LayoutParams params = (FrameLayout.LayoutParams) mFloatPLVideoTextureView.getLayoutParams();
params.width = DimensUtil.dpToPixels(this, 160);
params.height = height * DimensUtil.dpToPixels(this, 160) / width;
mFloatPLVideoTextureView.setLayoutParams(params);
}


/**
* 重新给视频播放器设置大小--主要是activity这种设置的PLOnVideoSizeChangedListener在首次会失效
*
* @param width 视频相对宽度
* @param height 视频相对高度
*/
private void resize(int width, int height) {
int playWidth = mVideoLayout.getWidth();
int playHeight = mVideoLayout.getHeight();
// 重新设置视频的播放大小
FrameLayout.LayoutParams params = (FrameLayout.LayoutParams) mVideoLayout.getVideoTextureGroup().getLayoutParams();
// 播放区域
if (playWidth * height < playHeight * width) {
// 视频宽高比大于父布局宽高比--取宽算高
params.width = playWidth;
params.height = playWidth * height / width;
} else {
// 视频宽高比不大于父布局宽高比--取高算宽
params.height = playHeight;
params.width = playHeight * width / height;
}
params.gravity = Gravity.CENTER;

mVideoLayout.getVideoTextureGroup().setLayoutParams(params);
}
//--------------------------视频相关 end------------------------

/**
* 收藏状态
*/
private void changeCollectState(int hasCollect) {
this.hasCollect = hasCollect;
Drawable drawable;
if (hasCollect == 1) {
drawable = getResources().getDrawable(R.drawable.ic_collect_sel);
collectTv.setText("已收藏");
} else {
drawable = getResources().getDrawable(R.drawable.ic_collect_nor);
collectTv.setText("收藏");
}
collectTv.setCompoundDrawablesWithIntrinsicBounds(null, drawable, null, null);

EventRefreshBean eventRefreshBean = new EventRefreshBean();
eventRefreshBean.setRefreshGoodsCollection(true);
EventBus.getDefault().post(eventRefreshBean);
}

/**
* 底部按钮各个场景显示切换
*/
private boolean isSupportPackage;

private void changeFooterBtn(ProDetailBean proDetailBean, int type) {
switch (type) {
case 0: //正常获取到商品数据返回结果是000情况
if (StringUtils.isEmpty(proDetailBean.getGroupActivityId())) {
//非拼团商品详情
groupBuyLl.setVisibility(View.GONE);
if (proDetailBean.getStoreCount() <= 0) {
sellOutBtn.setVisibility(View.VISIBLE);
addCartBuyLl.setVisibility(View.GONE);
buyNowBtn.setVisibility(View.GONE);
if (!stockType.equals("0")) {
sellOutBtn.setText(ListUtils.isEmpty(proDetailBean.getDeliveryTypeList()) ? "暂不支持线上销售" : "暂不支持购买");
} else {
for (DeliveryTypeBean deliveryTypeBean : proDetailBean.getDeliveryTypeList()) {
if (deliveryTypeBean.getDeliveryTypeId() == 4 && !StringUtils.isEmpty(regionCode)) {
sellOutBtn.setVisibility(View.GONE);
addCartBuyLl.setVisibility(View.VISIBLE);
buyNowBtn.setVisibility(View.VISIBLE);
break;
}
}
sellOutBtn.setText("商品补货中");
}
sellOutBtn.setText("商品补货中");
return;
}

addCartBuyLl.setVisibility(View.VISIBLE);
switch (stockType) {
case "1": //快速送
case "2": //次日达
buyBtn.setVisibility(View.GONE);
break;
default:
boolean supportBuy = false;
if (!ListUtils.isEmpty(proDetailBean.getDeliveryTypeList())) {
for (DeliveryTypeBean deliveryTypeBean : proDetailBean.getDeliveryTypeList()) {
String deliveryBusinessType = deliveryTypeBean.getDeliveryBusinessType();
if (deliveryBusinessType.equalsIgnoreCase("1") ||
deliveryBusinessType.equalsIgnoreCase("3")) {
//快递配送
supportBuy = true;
break;
}
}
}
if (supportBuy) {
buyBtn.setVisibility(View.VISIBLE);
} else {
buyBtn.setVisibility(View.GONE);
}
if (proDetailBean.getIsPreSale() == 1) {
addCartBtn.setVisibility(View.GONE);
//预售商品
switch (proDetailBean.getPreSaleStatus()) {
case 1:
//未开始
buyBtn.setText("敬请期待");
buyBtn.setEnabled(false);
buyBtn.setBackgroundColor(Color.parseColor("#38a4ee"));

break;
case 2:
buyBtn.setBackgroundColor(getResources().getColor(R.color.main_color));
break;
case 3:
if (supportBuy) {
buyBtn.setText("预售结束");
buyBtn.setEnabled(false);
buyBtn.setBackgroundColor(getResources().getColor(R.color.light_text_color));
} else {
buyNowBtn.setText("预售结束");
buyNowBtn.setEnabled(false);
buyNowBtn.setBackgroundColor(getResources().getColor(R.color.light_text_color));
}
break;
default:
break;
}
} else {
addCartBtn.setVisibility(View.VISIBLE);
buyBtn.setBackgroundColor(getResources().getColor(R.color.main_color));
}
break;
}
} else {
addCartBuyLl.setVisibility(View.GONE);
if (proDetailBean.getStoreCount() == 0) {
groupBuyLl.setVisibility(View.GONE);
buyNowBtn.setVisibility(View.GONE);
sellOutBtn.setVisibility(View.VISIBLE);
sellOutBtn.setText("商品补货中");
}

if (proDetailBean.getGroupStauts() == 1) {
groupBuyNowBtn.setText("查看我的团");
openGroupBtn.setText("邀请好友");
} else {
if (proDetailBean.getStoreCount() < BaseParser.parseInt(proDetailBean.getGroupNum())&&proDetailBean.getStoreCount()>0) {
tvDetailStockHint.setVisibility(View.VISIBLE);
StringUtils.setText(tvDetailStockHint, "库存不足" + proDetailBean.getGroupNum() + "件");
} else {
tvDetailStockHint.setVisibility(View.GONE);
}
groupBuyNowBtn.setText((proDetailBean.getMemberPrice().contains("~") ?
StringConstantUtils.RMB_SIGN + proDetailBean.getPrice().split("~")[0] :
StringConstantUtils.RMB_SIGN + proDetailBean.getMemberPrice()) + "\n" + "直接购买");

openGroupBtn.setText((proDetailBean.getGroupPrice().contains("~") ?
StringConstantUtils.RMB_SIGN + proDetailBean.getGroupPrice().split("~")[0] :
StringConstantUtils.RMB_SIGN + proDetailBean.getGroupPrice()) + "\n" + proDetailBean.getGroupNum() + "人团");
}
}
break;
case 1: //请求异常返回002已下架
sellOutBtn.setVisibility(View.GONE);
addCartBuyLl.setVisibility(View.GONE);
groupBuyLl.setVisibility(View.GONE);
buyNowBtn.setVisibility(View.VISIBLE);
buyNowBtn.setText("已下架");
buyNowBtn.setEnabled(false);
buyNowBtn.setBackgroundColor(Color.parseColor("#c9c9c9"));
setOffShelfView();
break;
default:
break;
}

priceDiscussLl.setVisibility(View.GONE);
if (!StringUtils.isEmpty(proDetailBean.getMemberPriceLabel())) {
priceDiscussLl.setVisibility(View.VISIBLE);
priceDisCussTv.setText(proDetailBean.getMemberPriceLabel());
PriceDiscussIv.setBackgroundResource(StringUtils.isEmpty(proDetailBean.getBusinessMobile()) ? 0 : R.drawable.ic_mianyi);
return;
}
if (!ListUtils.isEmpty(proDetailBean.getBelongIndustryList())) {
if (proDetailBean.getBelongIndustryList().contains("1")) {
btnCatering.setVisibility(View.VISIBLE);
for (DeliveryTypeBean deliveryTypeBean : proDetailBean.getDeliveryTypeList()) {
if (deliveryTypeBean.getDeliveryTypeId() == 2) {
isSupportPackage = true;
break;
}
}
btnCatering.setText(isSupportPackage ? "加入购物车" : "前往点餐专区下单>");
} else {
btnCatering.setVisibility(View.GONE);
}
}

}

/**
* 错误信息页面展示
*/
private void showErrorInfo(boolean isError, String errorTips) {
if (!NetUtil.isNetworkConnected(this)) {
proDetailFooterLl.setVisibility(View.GONE);
}
if (isError) {
proEmptyLl.setVisibility(View.VISIBLE);
proDetailBannerRl.setVisibility(View.GONE);
sdlPro.setVisibility(View.GONE);
proDetailFooterLl.setVisibility(View.GONE);
detailErrorInfoTv.setText(errorTips);
} else {
proDetailFooterLl.setVisibility(View.VISIBLE);
proEmptyLl.setVisibility(View.GONE);
proDetailBannerRl.setVisibility(View.VISIBLE);
sdlPro.setVisibility(View.VISIBLE);
}
}


@OnClick({R.id.contact_guider_rl, R.id.collect_rl, R.id.cart_rl, R.id.sell_out_btn,
R.id.buy_now_btn, R.id.add_cart_btn, R.id.buy_btn, R.id.group_buy_now_btn,
R.id.open_group_btn, R.id.price_discuss_ll, R.id.btn_catering, R.id.iv_video_close})
public void onClick(View view) {
switch (view.getId()) {
case R.id.contact_guider_rl://联系导购
//记录最后访问的商品信息,方便在IM页提示用户是否发送该信息
if (proDetailBean == null) {
return;
}
IMMessageModel productMsgModel = CustomMessageCreater.createGoodsCustomMessage(proDetailBean);
PoiHelper.saveLatestVisitGoods(productMsgModel);
PoiHelper.setIsFromProductPage(true);
MobclickAgent.onEvent(ProDetailActivity.this, "goodsDetailContactEvent");
//商品详情页-联系导购按钮埋点
// IMHelper.getInstance().contactNormal(this, IMContactInfoHelper.getInstance().getGuiderInfo());
if (UnifiedCustomerService.isUseCustomerServiceByDefault()) {
if (SysHelper.getUdeskStatus()) {
LdyUdeskHelper.getInstance().entryChat(proDetailBean, storeId, LdyUdeskProDetatl.PRO_DETAIL_NORMAL);
}else{
// StartPrivateChatEvent startPrivateChatEvent = new StartPrivateChatEvent();
// startPrivateChatEvent.setIMMessageModel(productMsgModel);
// startPrivateChatEvent.setFromProDetail(true);
// EventBus.getDefault().postSticky(startPrivateChatEvent);
// RongHelper.getInstance().startPrivateChat(this, Constants.cust.getGuiderId() + "");
}
} else {
StartPrivateChatEvent startPrivateChatEvent = new StartPrivateChatEvent();
startPrivateChatEvent.setIMMessageModel(productMsgModel);
startPrivateChatEvent.setFromProDetail(true);
EventBus.getDefault().postSticky(startPrivateChatEvent);
//region add by shenbh on 2019/8/21 添加导购id
int serviceGuideId = Constants.cust.getGuiderId();
if (proDetailBean != null && proDetailBean.getServiceGuide() != 0){
serviceGuideId = proDetailBean.getServiceGuide();
}
//endregion add
RongHelper.getInstance().startPrivateChat(this, serviceGuideId + "");
}
break;
case R.id.collect_rl: //收藏
int favorType = hasCollect == 0 ? 1 : 0;
productPresenter.submitNewCustomerFavor(Constants.getCustomerId(), BaseParser.parseInt(proType), storeId, proId, favorType);
break;
case R.id.cart_rl: //购物车跳转
//商品详情页-购物车入口埋点
if (proDetailBean==null){
return;
}
MobclickAgent.onEvent(ProDetailActivity.this, "goodsDetailCartEvent");
Intent intent = new Intent();
intent.setClass(this, ShopCartActivity.class);
intent.putExtra(ShopCardIntent.BUSINESS_ID, String.valueOf(proDetailBean.getBusinessId()));
intent.putExtra(ShopCardIntent.STORE_ID, storeId);
intent.putExtra(ShopCardIntent.SORT_TYPE, 0);
startActivity(intent, false);
break;
case R.id.sell_out_btn: //售罄
break;
case R.id.buy_now_btn:
if (skuDialog != null && proSkuInfoBean != null) {
skuDialog.setDataAndOperationType(proSkuInfoBean, NewProdetailSkuDialog.OPERATION_TYPE_TO_BUY);
skuDialog.show();
}
break;
case R.id.add_cart_btn: //正常商品模式下加入购物车
if (skuDialog != null && proSkuInfoBean != null) {
skuDialog.setDataAndOperationType(proSkuInfoBean, NewProdetailSkuDialog.OPERATION_TYPE_ADD_CART);
skuDialog.show();
}
break;

case R.id.price_discuss_ll: //价格面议
if (!StringUtils.isEmpty(proDetailBean.getMemberPriceLabel()) && !StringUtils.isEmpty(proDetailBean.getBusinessMobile())) {
showPriceDiscussDialog();
}
break;
case R.id.buy_btn: //正常商品模式下立即购买
if (skuDialog != null && proSkuInfoBean != null) {
skuDialog.setDataAndOperationType(proSkuInfoBean, NewProdetailSkuDialog.OPERATION_TYPE_TO_BUY);
skuDialog.show();
}
break;
case R.id.group_buy_now_btn: //拼团下立即购买
if (proDetailBean.getGroupStauts() == 1) {
UIHelper.startGroupOnDetail(ProDetailActivity.this, StringUtils.isEmpty
(proDetailBean.getGroupDetailId()) ? proDetailBean
.getGroupActivityId() : proDetailBean.getGroupDetailId(),
proDetailBean.getGroupStauts());
return;
}

if (skuDialog != null && proSkuInfoBean != null) {
skuDialog.setDataAndOperationType(proSkuInfoBean, NewProdetailSkuDialog.OPERATION_TYPE_GROUP_TO_BUY);
skuDialog.show();
}
break;
case R.id.open_group_btn://立即开团
if (skuDialog != null && proSkuInfoBean != null) {
skuDialog.setDataAndOperationType(proSkuInfoBean, NewProdetailSkuDialog.OPERATION_TYPE_OPEN_GROUP);
}

if (proDetailBean.getGroupStauts() == 1) {
// [BUG:31688]处理点击邀请好友--分享海报无效的问题
mCurShareWay=INVITE_SHARE;
UIHelper.shareGroupDetailDialog(ProDetailActivity.this, proDetailBean
.getGroupDetailId(), proDetailBean.getSurplusNum() + "", "",
Constants.cust.getStoreId(), proDetailBean
.getTitle(), proDetailBean.getPicUrl(), "", "", mShareCallback);
return;
}
skuDialog.show();
break;
case R.id.btn_catering://餐饮专区下单
if (isSupportPackage) {
if (skuDialog != null && proSkuInfoBean != null) {
skuDialog.setDataAndOperationType(proSkuInfoBean, NewProdetailSkuDialog.OPERATION_TYPE_ADD_CART);
skuDialog.show();
}
} else {
UIHelper.goCateringEntranceActivity(this);
}
break;
case R.id.iv_video_close://点击可关闭悬浮视频
closeFloatVideo();
break;
default:
break;
}
}

/**
* 价格面议联系弹窗
*/
private void showPriceDiscussDialog() {
if (confirmDialog == null) {
confirmDialog = new ConfirmDialog(this);
}
confirmDialog.getTopTittle().setVisibility(View.VISIBLE);
confirmDialog.getTopTittle().setText("拨打商家客服电话");
confirmDialog.getTitleView().setText(proDetailBean.getBusinessMobile());
confirmDialog.getRightButton().setText("呼叫");
confirmDialog.getRightButton().setTextColor(Color.parseColor("#ff5252"));
confirmDialog.setRightButtonListener(new View.OnClickListener() {
@Override
public void onClick(View view) {
String[] permissionG = new String[]{CALL_PHONE};
requestPermission(new OnPermissionListener() {
@Override
public void onPermissionSuccessful() {
toCallPhone();
}

@Override
public void onPermissionFailure() {
}
}, permissionG);
confirmDialog.dismiss();

}
});
confirmDialog.setLeftButtonListener(new View.OnClickListener() {
@Override
public void onClick(View view) {
confirmDialog.dismiss();
}
});
confirmDialog.show();
}

@RequiresPermission(CALL_PHONE)
private void toCallPhone() {
Intent intent = new Intent(Intent.ACTION_CALL, Uri.parse("tel:" + proDetailBean.getBusinessMobile()));
startActivity(intent);
}

/**
* 购买商品回调
*
* @param selectValue 选择的Sku、数量等值
* @param btn 控制点击的控件
*/
@Override
public void buyNow(Map<String, Object> selectValue, Button btn) {
/*修改实施反馈bug 24855 点击直接购买跳转拼团订单提交页 modify by yelk 2018/02/28*/
switch (skuDialog.getmOperationType()) {
case OPERATION_TYPE_OPEN_GROUP://开团
if (!StringUtils.isEmpty(proDetailBean.getGroupActivityId()) && !ListUtils.isEmpty(proDetailBean.getGroupList())) {
productPresenter.joinGroupImmediately(proDetailBean, selectValue);
} else {
if (!StringUtils.isEmpty(storeId) && !TextUtils.equals("0",storeId)){
productPresenter.submitBuyItem(btn, proDetailBean, skuDialog.getmOperationType(),
skuDialog.paramsMap(proType, storeId, liveId, liveType,"0","0","",""));
} else {
productPresenter.submitBuyItem(btn, proDetailBean, skuDialog.getmOperationType(),
skuDialog.paramsMap(proType, "", liveId, liveType, "0", "0", "", ""));
}
}
break;
default:
if (!StringUtils.isEmpty(storeId) && !TextUtils.equals("0",storeId)){
productPresenter.submitBuyItem(btn, proDetailBean, skuDialog.getmOperationType(),
skuDialog.paramsMap(proType, storeId, liveId, liveType, "0", "0", "", ""));
} else {
productPresenter.submitBuyItem(btn, proDetailBean, skuDialog.getmOperationType(),
skuDialog.paramsMap(proType, "", liveId, liveType, "0", "0", "", ""));
}
break;
}
}

/**
* 加入购物车回调
*
* @param selectValue 选择的Sku、数量等值
* @param btn 控制点击的控件
*/
@Override
public void addCart(Map<String, Object> selectValue, Button btn) {
productPresenter.submitShopCart(btn, skuDialog.paramsMap(proType, storeId, liveId, liveType,"0","0","",""));
}

/**
* 套餐信息回调
*
* @param discountPackageBean
*/
@Override
public void packageItemInfoCallback(ProDetailPackageInfoBean discountPackageBean) {
mealView.setTotalPackNum(discountPackageBean.getPackageNum());
mealView.setData(discountPackageBean, storeId, BaseParser.parseInt(proId));
}


@OnClick(R.id.iv_share)
public void onViewClicked(View view) {
switch (view.getId()) {
case R.id.iv_share:
if (proDetailBean != null) {
share();
}
break;
default:
break;
}
}

/**
* 分享商品
*/
private void share() {
mCurShareWay=TOP_SHARE;
if (proDetailBean == null) {
return;
}
if (!StringUtils.isEmpty(proDetailBean.getTitle())) {
MobclickAgent.onEvent(this, "goodsDetailShareEvent");
//商品详情页-分享图标埋点
String title = proDetailBean.getTitle();
String summary = proDetailBean.getBusinessName();
/*
分享H5链接
*/
String targeturl = String.format
("%s/businessItemDetail?businessItemId=%s&itemType=%s&shareAgentId=%s" +
"&app=1&storeId=%s", Constants.getLdyH5Url(), proDetailBean
.getLocalItemId(), proDetailBean.getItemType(), Constants.getCustomerId(), storeId);

String imageurl = "";
if (!StringUtils.isEmpty(proDetailBean.getPicUrl())) {
imageurl = proDetailBean.getPicUrl();
}
ShareBean shareData = new ShareBean();
shareData.setTitle(title);
shareData.setContent(summary);
shareData.setImageDesc(imageurl);

QRCodeExtra qrCodeExtra = new QRCodeExtra();
qrCodeExtra.setQrName("扫码查看更多商品信息");
shareData.setExtra(qrCodeExtra);

if (proDetailBean.getBusinessId() == BaseParser.parseInt(Constants.cust.getBusinessId())) {
shareData.setTargetUrl(H5UrlCommonParams.withCustomerShareCommonParams(targeturl));
} else {
shareData.setTargetUrl(H5UrlCommonParams.withCustomerShareCommonParams
(targeturl + "&fromFound=1"));// 不显示领取会员身份
}
// Platform[] platforms = SharePlatformCenter.getSharePlatform(shareData);
Platform[] platforms=SharePlatformCenter.getSharePlatform(SharePlatformTemplet.POSTER_WITHOUT_MINI_PROGRAM);
//当开启分享得积分的时候使用相应的弹窗和回调
ShareBusinessConfigBean shareBusinessConfigBean = SysHelper.getShareBusinessConfig();
if (shareBusinessConfigBean.isOpenShareItemSendIntegral()) {
IntegralShareCallBack integralShareCallBack = new IntegralShareCallBack(this);
integralShareCallBack.setShareType(DoubleTipsShareDialog.INTEGRAL_TYPE_PRODUCT);
integralShareCallBack.setShareTypeId(proDetailBean.getLocalItemId());
DoubleTipsShareDialog doubleTipsShareDialog = new DoubleTipsShareDialog(this);
doubleTipsShareDialog.setIntegralTipInfo(shareBusinessConfigBean
.getShareItemIntegralNum(), shareBusinessConfigBean
.getMaxItemIntegralNum(), DoubleTipsShareDialog
.INTEGRAL_TYPE_PRODUCT);
ShareUtil.share(this, shareData, platforms, doubleTipsShareDialog, integralShareCallBack);
} else {
// 处理未开启积分点击海报无响应--主要是因为海报的回调机制与其他的不太一样(二次回调,详细可以查看对应的QrPosterEngine实现说明)
ShareUtil.share(this, shareData, platforms, null, mShareCallback);
}
}
}

/**
* 获取未读消息数量
*
* @param imEvent
*/
@Subscribe(threadMode = ThreadMode.MAIN)
public void onEvent(IMEvent imEvent) {
// if (productDetailImNoticeTv != null)
// IMHelper.getInstance().setNormalUnRead(imEvent, productDetailImNoticeTv, false);
}

/**
* 刷新购物车数量及sku信息
*
* @param event
*/
@Subscribe(threadMode = ThreadMode.MAIN)
public void onEvent(ShopCartOtherEvent event) {
if (event.getType() == ShopCartOtherEvent.REFRESH_SHOP_CART_VIEW_NUM) {
setShopCarNum();
getSkuInfo();
}
}

/**
* 退出商品详情
*
* @param event
*/
@Subscribe(sticky = true, threadMode = ThreadMode.MAIN)
public void onEvent(CloseProductDetailEvent event) {
finish();
}

/**
* 加载商品详情数据
*
* @param event
*/
@Subscribe(threadMode = ThreadMode.MAIN)
public void onEvent(RefreshProductInfoEvent event) {
if (event != null) {
loadDetailData();
if (event.isChangeStatusBar()) {
getImmersion().statusBarColor2(R.color.black, false);
} else {
getImmersion().statusBarColor2(isCollapsedState ? R.color.u1city_frame_toolbar_bg_color : R.color.transparents, true);
}
}
}


@Override
public void onBackPressed() {
if (App.getContext().getAppIndexingManager().onBackPress(this, getIntent())) {
finish();
} else {
super.onBackPressed();
}
}

@Override
protected void onPause() {
super.onPause();
// 跳转其他页面不可见的时候,暂停正在播放的视频
if (mVideoLayout != null && mVideoLayout.getPlayerState() == PlayerState.PLAYING) {
mVideoLayout.pauseCurVideoView();
}
}

@Override
protected void onDestroy() {
super.onDestroy();
RequestApi.getInstance().cancleAll(this);
proDetailCountDownView.cancelCountDown();
EventBus.getDefault().unregister(this);
ButterKnife.unbind(this);
if (shareGetIntegralPopupWindow != null) {
shareGetIntegralPopupWindow.cancel();
}
//-------视频相关--------
if (mVideoLayout != null) {
mVideoLayout.onActivityDestroy();
}
if (mFloatPLVideoTextureView != null) {
mFloatPLVideoTextureView.stopPlayback();
}
//-------视频相关end--------
}

private ShareWithPosterDialog mShareWithPosterDialog;
/**
* 分享显示海报弹窗
*/
public void showPosterDialog() {
// 2018/9/13 展示海报
if (mShareWithPosterDialog == null) {
mShareWithPosterDialog = new ShareWithPosterDialog(this);
mShareWithPosterDialog.setSave2AlbumCallback(new BargainDetailActivity.Save2AlbumCallback() {
@Override
public void saveSuccess(String path) {
// 保存后需要广播通知系统刷新,否则直接进相册会找不到
mContext.sendBroadcast(
new Intent(Intent.ACTION_MEDIA_SCANNER_SCAN_FILE, Uri.fromFile(FileUtils.getFileByPath(path))));
closeSharePosterDialogSafely();
showToast("保存成功");
}

@Override
public void saveFail() {
showToast("图片保存失败");
}

@Override
public void shareCompleted(int shareStatus) {
handleShareResultByStatusCode(shareStatus);
}
});
}

// 处理单价以及当前所参加的活动类型
@PromotionTypeOfProDetailAnn int promotionType = getPromotionType();
// 2018/9/13 原价也会有区间的情况要处理
double oriPrice=getTopOri();
double bestPrice=getBestPrice(promotionType);
String shareUrl=getShareUrl();
// 2018/9/13 这个折扣的标签从后台取
mShareWithPosterDialog.showDialog(PosterShareDialogType.FROM_PRO_DETAIL,proDetailBean.getPicUrl(),proDetailBean.getTitle(),bestPrice,oriPrice,shareUrl,promotionType,proDetailBean.getPromotionTag());
}

/**
* 获取海报分享的Url<br></br>
* 主要是区分是否为拼团商品(砍价商品已经在{@link xxx.xxx.view.bargain.product.BarginProDetailActivity}中处理)
* @return 商品对应的海报分享Url
*/
private String getShareUrl() {
//[BUG:31602]拼团二维码Url判断字段修改
// 2018/10/12 根据不同的分享获取不同的url
return (mCurShareWay==TOP_SHARE)?proDetailBean.getItemDetailPosterUrl():proDetailBean.getGroupDetailPosterUrl();
}

/**
* 获取商品参加的活动类型,0标识不参加任何活动
*/
@CheckResult
private int getPromotionType() {
// [BUG:31602]同时处在折扣和拼团时,折扣优先
if(!TextUtils.isEmpty(proDetailBean.getGroupActivityId())){
return PromotionTypeOfProDetailAnn.GROUP;
}
if(1==proDetailBean.getIsPromotion()){
return PromotionTypeOfProDetailAnn.DISCOUNT;
}
// 砍价类型商品详情页在BargainDetailActivity
return PromotionTypeOfProDetailAnn.NONE;
}

/**
* 非拼团商品直接从memberPrice获取最优价,拼团的直接从groupPrice价格区间获取
* SKU会是一个区间
* @param promotionType 活动类型
*/
private double getBestPrice(@PromotionTypeOfProDetailAnn int promotionType) {
String bestPrice;
// [BUG:31676]处理拼团商品详情分享的价格不是最低价的问题
if(promotionType== PromotionTypeOfProDetailAnn.GROUP){
bestPrice=proDetailBean.getGroupPrice();
}else {
bestPrice=proDetailBean.getMemberPrice();
}

if(bestPrice==null){
return 0.0D;
}
if(bestPrice.contains("~")){
String[] split = bestPrice.split("~");
return Double.parseDouble(split[0]);
}
return Double.parseDouble(bestPrice);
}

/**
* 获取原价
*/
private double getTopOri(){
String price = proDetailBean.getPrice();
if(price==null){
return 0.0D;
}
if(price.contains("~")){
String[] split = price.split("~");
return Double.parseDouble(split[0]);
}
return Double.parseDouble(price);
}

// 一般的分享回调--用于区别积分回调
private ShareCallback mShareCallback=new ShareCallback() {
@Override
public void onShareComplete(int status, Platform platform) {
handleShareResultByStatusCode(status);
}
};
/**
* 处理分享结果返回的状态码
* @param shareStatus 分享状态码
*/
private void handleShareResultByStatusCode(int shareStatus){
switch (shareStatus) {
case ShareCallback.STATUS_SUCCUSS: {
closeSharePosterDialogSafely();
showToast("分享成功");
break;
}
case ShareCallback.STATUS_QR_POSTER:{
showPosterDialog();
break;
}
case ShareCallback.STATUS_FAILD: {
showToast("分享失败");
break;
}
case ShareCallback.STATUS_CANCEL: {
closeSharePosterDialogSafely();
showToast("取消分享");
break;
}
case ShareCallback.STATUS_COPY:{
showToast("链接已复制");
break;
}

default:
break;
}
}

/**
* 关闭海报分享弹窗
*/
private void closeSharePosterDialogSafely(){
if(mShareWithPosterDialog!=null){
mShareWithPosterDialog.dismiss();
}
}
}

布局文件

Activity的布局文件

activity_pro_detail_new.xml –>其中content_pro_detail内有各个自定义样式布局

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
<?xml version="1.0" encoding="utf-8"?>
<LinearLayout ...>
<android.support.design.widget.CoordinatorLayout
...
android:layout_weight="1">

<!--空布局 begin-->
<LinearLayout
...
android:visibility="gone">
...
</LinearLayout>
<!--空布局 end-->

<xxx.xxx.view.productDetail.widget.SlideDetailsLayout
...>

<!--具体内容 begin-->
<include layout="@layout/content_pro_detail"/>
<!--具体内容 end-->

<!--H5相关 begin-->
<LinearLayout
...>
<xxx.xxx.view.productDetail.TouchWebView
...
android:scrollbars="none"/>
</LinearLayout>
<!--H5相关 end-->
</xxx.xxx.view.productDetail.widget.SlideDetailsLayout>

<!--商品标题信息 begin-->
<android.support.v7.widget.Toolbar
...>
...
</android.support.v7.widget.Toolbar>
<!--商品标题信息 end-->



<!--底部客服、收藏、购物车、加入购物车、立即购买等控件 begin-->
<RelativeLayout
...>
...
</RelativeLayout>
<!--底部客服、收藏、购物车、加入购物车、立即购买等控件 end-->

</LinearLayout>

content_pro_detail.xml

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
<?xml version="1.0" encoding="utf-8"?>
<android.support.v4.widget.NestedScrollView
...
android:descendantFocusability="blocksDescendants"
android:scrollbars="none">

<LinearLayout
...
android:orientation="vertical">

...

<!-- 商品价格 -->
<xxx.xxx.view.productDetail.ui.ProDetailPriceUI
android:id="@+id/pro_detail_price_view"
android:layout_width="match_parent"
android:layout_height="wrap_content" />

<!--商品标题跨境-->
<xxx.xxx.view.productDetail.ui.ProDetailTitleInfoUI
android:id="@+id/pro_detail_title_info_view"
android:layout_width="match_parent"
android:layout_height="wrap_content" />
...

<!-- 继续拖动提示-->
<include layout="@layout/pro_detail_drap_tips" />

</LinearLayout>
</android.support.v4.widget.NestedScrollView>

实现通用的圆角布局

使用 canvas.drawPath(path, paint),为 paint 添加抗锯齿标记,并设置 XFermodes

在 Android P 上无法使用 canvas.drawPath(path, paint) 剪裁布局,原因是 Android P 上 XFermodes 行为变更导致的。暂且在 P 上使用 canvas.clipPath(path) 实现圆角,会有锯齿效果

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
public class RoundRelativeLayout extends RelativeLayout {
private Path mPath;
private Paint mPaint;
private RectF mRectF;
private float mRadius;
private boolean isClipBackground;

public RoundRelativeLayout(@NonNull Context context) {
this(context, null);
}
public RoundRelativeLayout(@NonNull Context context, @Nullable AttributeSet attrs) {
this(context, attrs, 0);
}
public RoundRelativeLayout(@NonNull Context context, @Nullable AttributeSet attrs, int defStyleAttr) {
super(context, attrs, defStyleAttr);

TypedArray ta = context.obtainStyledAttributes(attrs, R.styleable.RoundRelativeLayout);
mRadius = ta.getDimension(R.styleable.RoundRelativeLayout_rlRadius, 0);
isClipBackground = ta.getBoolean(R.styleable.RoundRelativeLayout_rlClipBackground, true);
ta.recycle();

mPath = new Path();
mPaint = new Paint(Paint.ANTI_ALIAS_FLAG);
mRectF = new RectF();

mPaint.setXfermode(new PorterDuffXfermode(PorterDuff.Mode.DST_IN));
}
public void setRadius(float radius) {
mRadius = radius;
postInvalidate();
}
@Override
protected void onSizeChanged(int w, int h, int oldw, int oldh) {
super.onSizeChanged(w, h, oldw, oldh);
mRectF.set(0, 0, w, h);
}
@SuppressLint("MissingSuperCall")
@Override
public void draw(Canvas canvas) {
if (Build.VERSION.SDK_INT >= 28) {
draw28(canvas);
} else {
draw27(canvas);
}
}
@Override
protected void dispatchDraw(Canvas canvas) {
if (Build.VERSION.SDK_INT >= 28) {
dispatchDraw28(canvas);
} else {
dispatchDraw27(canvas);
}
}
private void draw27(Canvas canvas) {
if (isClipBackground) {
canvas.saveLayer(mRectF, null, Canvas.ALL_SAVE_FLAG);
super.draw(canvas);
canvas.drawPath(genPath(), mPaint);
canvas.restore();
} else {
super.draw(canvas);
}
}
private void draw28(Canvas canvas) {
if (isClipBackground) {
canvas.save();
canvas.clipPath(genPath());
super.draw(canvas);
canvas.restore();
} else {
super.draw(canvas);
}
}
private void dispatchDraw27(Canvas canvas) {
canvas.saveLayer(mRectF, null, Canvas.ALL_SAVE_FLAG);
super.dispatchDraw(canvas);
canvas.drawPath(genPath(), mPaint);
canvas.restore();
}
private void dispatchDraw28(Canvas canvas) {
canvas.save();
canvas.clipPath(genPath());
super.dispatchDraw(canvas);
canvas.restore();
}
private Path genPath() {
mPath.reset();
mPath.addRoundRect(mRectF, mRadius, mRadius, Path.Direction.CW);
return mPath;
}
}

attrs.xml

1
2
3
4
<declare-styleable name="RoundRelativeLayout">
<attr name="rlRadius" format="dimension" />
<attr name="rlClipBackground" format="boolean" />
</declare-styleable>