仿饿了么加入购物车旋转控件 - 自带闪转腾挪动画 的按钮

Android · zxt0601 · Created at · Last by mm0089 Replied at · 2392 hits
3424 1482115434

想看我更多文章:【张旭童的博客】
想来gayhub和我gaygayup:【mcxtzhang Github主页】
代码传送门:喜欢的话,随手点个star。多谢
https://github.com/mcxtzhang/AnimShopButton

经济上支持我 or 想通过视频看我是怎么实现的:

http://edu.csdn.net/course/detail/3898

概述

上文,酷炫Path动画已经预告了,今天给大家带来的是利用 纯自定义View,实现的仿饿了么加入购物车控件,自带闪转腾挪动画的按钮。
效果图如下:

图1 项目中使用的效果,考虑到了View回收复用
并且可以看到在RecyclerView中使用,切换LayoutManager也是没有问题的,
项目中使用的效果

图2 Demo效果,测试各种属性值
图2 Demo效果,测试各种属性值

注意,本控件非继承自ViewGroup,而是纯自定义View实现。理由如下:

  • 1 减少布局层级,从而提高性能
  • 2 文字和图形纯draw,用到什么draw什么,没有其他的额外工作,也间接提高性能。
  • 3 纯自定义View难度更高,更有实(装)践(B)的意义

1 减少布局层次,很好理解,ViewGroup内嵌套几个TextView、ImageV这里写代码片iew也可以实现这个效果,然而这会使布局层次多了一级,并且内部要嵌套多个控件,层级越多,控件越多,绘制的就越慢,在列表中对性能的影响更大。

2 别小看了“小小”的TextView和的ImageView,其实它们有很多的属性和特性在本例中是不必要的,举个例子,查看源码,TextView一万多行ondraw()方法有一百多行, ImageView1588行,这么多行代码都是我们需要的吗?直接使用这些现成的控件嵌套实现,其实性能不如我们用到什么draw什么。唯一的好处可能就是比较简单了。(其实TextView的性能是不高的)

3 纯自定义Viewdraw出这些需要的元素,并且还要考虑动画,以及点击各区域的监听,实现起来还是有一些难度的,但我们多写一些有难度的代码才能提高水平。

如何使用

伸手党福利:讲解实现前,先看一下如何使用 以及支持的属性等。

使用

xml:

    <!--使用默认UI属性-->
    <com.mcxtzhang.lib.AnimShopButton
        android:id="@+id/btn1"
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        app:maxCount="3"/>
    <!--设置了两圆间距-->
    <com.mcxtzhang.lib.AnimShopButton
        android:id="@+id/btn2"
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        app:count="3"
        app:gapBetweenCircle="90dp"
        app:maxCount="99"/>
    <!--仿饿了么-->
    <com.mcxtzhang.lib.AnimShopButton
        android:id="@+id/btnEle"
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        app:addEnableBgColor="#3190E8"
        app:addEnableFgColor="#ffffff"
        app:hintBgColor="#3190E8"
        app:hintBgRoundValue="15dp"
        app:hintFgColor="#ffffff"
        app:maxCount="99"/>

注意:
加减点击后,具体的操作,要根据业务的不同来编写了,设计到实际的购物车可能还有写数据库操作,或者请求接口等,要操作成功后才执行动画、或者修改count,这一块代码每个人写法可能不同。
使用时,可以重写onDelClick()onAddClick()方法,并在合适的时机回调onCountAddSuccess()onCountDelSuccess()以执行动画。

效果图如图2.

支持的属性

name format description 中文解释
isAddFillMode boolean Plus button is opened Fill mode default is stroke (false) 加按钮是否开启fill模式 默认是stroke(false)
addEnableBgColor color The background color of the plus button 加按钮的背景色
addEnableFgColor color The foreground color of the plus button 加按钮的前景色
addDisableBgColor color The background color when the button is not available 加按钮不可用时的背景色
addDisableFgColor color The foreground color when the button is not available 加按钮不可用时的前景色
isDelFillMode boolean Plus button is opened Fill mode default is stroke (false) 减按钮是否开启fill模式 默认是stroke(false)
delEnableBgColor color The background color of the minus button 减按钮的背景色
delEnableFgColor color The foreground color of the minus button 减按钮的前景色
delDisableBgColor color The background color when the button is not available 减按钮不可用时的背景色
delDisableFgColor color The foreground color when the button is not available 减按钮不可用时的前景色
radius dimension The radius of the circle 圆的半径
circleStrokeWidth dimension The width of the circle 圆圈的宽度
lineWidth dimension The width of the line (+ - sign) 线(+ - 符号)的宽度
gapBetweenCircle dimension The spacing between two circles 两个圆之间的间距
numTextSize dimension The textSize of draws the number 绘制数量的textSize
maxCount integer max count 最大数量
count integer current count 当前数量
hintText string The hint text when number is 0 数量为0时,hint文字
hintBgColor color The hint background when number is 0 数量为0时,hint背景色
hintFgColor color The hint foreground when number is 0 数量为0时,hint前景色
hingTextSize dimension The hint text size when number is 0 数量为0时,hint文字大小
hintBgRoundValue dimension The background fillet value when number is 0 数量为0时,hint背景圆角值

这么多属性够你用了吧。

下面看重点的实现吧,Let's Go!.

实现解剖

关于自定义View的基础,这里不再赘述。
如果阅读时有不明白的,建议下载源码边看边读,或者学习自定义View基础知识后再阅读本文。
代码传送门:喜欢的话,随手点个star。多谢
https://github.com/mcxtzhang/AnimShopButton


我们捡重点说,无非是绘制。
绘制的重点,这里分三块:

  • 静态绘制。(分两块:加减按钮和数量、hint提示文字和背景)
  • 第一层。(加减按钮和数量)以及它的旋转、位移、透明度动画
  • 第二层。(hint区域)以及它的伸展收缩动画

除了绘制以外的重点是:

  • 由于采用了完全的自定义View去实现这么一个“组合控件效果”,则点击事件的监听需要自己处理。
  • 在回收复用的列表中使用时,列表滑动,如何正确显示UI。

静态绘制

静态绘制就是最基本的自定义View知识,绘制圆圈(Circle)、线段(Line)、数字(Text)以及圆角矩形(RoundRect),值得注意的是,
要考虑到 避免overDraw和动画的需求,
我们要绘制的两层应该是互斥关系。

剥离掉动画代码,大致如下(基本都是draw代码,可以快速阅读):

@Override
    protected void onDraw(Canvas canvas) {
        if (isHintMode) {
            //hint 展开
            //背景
            mHintPaint.setColor(mHintBgColor);
            RectF rectF = new RectF(mLeft, mTop
                    , mWidth - mCircleWidth, mHeight - mCircleWidth);
            canvas.drawRoundRect(rectF, mHintBgRoundValue, mHintBgRoundValue, mHintPaint);
            //前景文字
            mHintPaint.setColor(mHintFgColor);
            // 计算Baseline绘制的起点X轴坐标
            int baseX = (int) (mWidth / 2 - mHintPaint.measureText(mHintText) / 2);
            // 计算Baseline绘制的Y坐标
            int baseY = (int) ((mHeight / 2) - ((mHintPaint.descent() + mHintPaint.ascent()) / 2));
            canvas.drawText(mHintText, baseX, baseY, mHintPaint);
        } else {
            //左边
            //背景 圆
            if (mCount > 0) {
                mDelPaint.setColor(mDelEnableBgColor);
            } else {
                mDelPaint.setColor(mDelDisableBgColor);
            }
            mDelPaint.setStrokeWidth(mCircleWidth);
            mDelPath.reset();
            mDelPath.addCircle(mLeft + mRadius, mTop + mRadius, mRadius, Path.Direction.CW);
            mDelRegion.setPath(mDelPath, new Region(mLeft, mTop, mWidth - getPaddingRight(), mHeight - getPaddingBottom()));
            canvas.drawPath(mDelPath, mDelPaint);

            //前景 -
            if (mCount > 0) {
                mDelPaint.setColor(mDelEnableFgColor);
            } else {
                mDelPaint.setColor(mDelDisableFgColor);
            }
            mDelPaint.setStrokeWidth(mLineWidth);
            canvas.drawLine(-mRadius / 2, 0,
                    +mRadius / 2, 0,
                    mDelPaint);

            //数量
            //是没有动画的普通写法,x left, y baseLine
            canvas.drawText(mCount + "", mLeft + mRadius * 2, mTop + mRadius - (mFontMetrics.top + mFontMetrics.bottom) / 2, mTextPaint);

            //右边
            //背景 圆
            if (mCount < mMaxCount) {
                mAddPaint.setColor(mAddEnableBgColor);
            } else {
                mAddPaint.setColor(mAddDisableBgColor);
            }
            mAddPaint.setStrokeWidth(mCircleWidth);
            float left = mLeft + mRadius * 2 + mGapBetweenCircle;
            mAddPath.reset();
            mAddPath.addCircle(left + mRadius, mTop + mRadius, mRadius, Path.Direction.CW);
            mAddRegion.setPath(mAddPath, new Region(mLeft, mTop, mWidth - getPaddingRight(), mHeight - getPaddingBottom()));
            canvas.drawPath(mAddPath, mAddPaint);
            //前景 +
            if (mCount < mMaxCount) {
                mAddPaint.setColor(mAddEnableFgColor);
            } else {
                mAddPaint.setColor(mAddDisableFgColor);
            }
            mAddPaint.setStrokeWidth(mLineWidth);
            canvas.drawLine(left + mRadius / 2, mTop + mRadius, left + mRadius / 2 + mRadius, mTop + mRadius, mAddPaint);
            canvas.drawLine(left + mRadius, mTop + mRadius / 2, left + mRadius, mTop + mRadius / 2 + mRadius, mAddPaint);
        }
    }

根据isHintMode 布尔值变量,区分是绘制第二层(Hint层)或者第一层(加减按钮层)。

绘制第二层时没啥好说的,就是利用canvas.drawRoundRect,绘制圆角矩形,然后canvas.drawText绘制hint。
(如果圆角的值足够大,矩形的宽度足够小,就变成了圆形。)

绘制第一层时,要根据当前的数量选择不同的颜色,注意在绘制加减按钮的圆圈时,我们是用Path绘制的,这是因为我们还需要用Path构建Region类,这个类就是我们监听点击区域的重点

点击事件的监听

在讲解动画之前,我们先说说如何监听点击的区域,因为本控件的动画是和加减数量息息相关的,而数量的加减是由点击相应"+ - 按钮"区域触发的。
所以我们的监听按钮的点击事件,其实就是监听相应的"+ - 按钮"区域

上一节中,我们在绘制"+ - 按钮"区域时,通过Path,构建了两个Region类,Region类有个contains(int x, int y)方法如下,通过传入对应触摸的x、y坐标,就可知道知否点击了相应区域

    /**
     * Return true if the region contains the specified point
     */
    public native boolean contains(int x, int y);

知道了这一点,再写这部分代码就相当简单了:

    @Override
    public boolean onTouchEvent(MotionEvent event) {
        int action = event.getAction();
        switch (action) {
            case MotionEvent.ACTION_DOWN:
                //hint模式
                if (isHintMode) {
                    onAddClick();
                    return true;
                } else {
                    if (mAddRegion.contains((int) event.getX(), (int) event.getY())) {
                        onAddClick();
                        return true;
                    } else if (mDelRegion.contains((int) event.getX(), (int) event.getY())) {
                        onDelClick();
                        return true;
                    }
                }
                break;
            case MotionEvent.ACTION_MOVE:
                break;
            case MotionEvent.ACTION_UP:
            case MotionEvent.ACTION_CANCEL:
                break;
        }
        return super.onTouchEvent(event);
    }

hint模式时,我们可以认为控件所有范围都是“+”的有效区域

而在非hint模式时,根据上一节构建的mAddRegionmDelRegion去判断。

判断确认点击后,具体的操作,要根据业务的不同来编写了,设计到实际的购物车可能还有写数据库操作,或者请求接口等,要操作成功后才执行动画、或者修改count,这一块代码每个人写法可能不同。
使用时,可以重写onDelClick()onAddClick()方法,并在合适的时机回调onCountAddSuccess()onCountDelSuccess()以执行动画。

本文如下编写:

    protected void onDelClick() {
        if (mCount > 0) {
            mCount--;
            onCountDelSuccess();
        }
    }

    protected void onAddClick() {
        if (mCount < mMaxCount) {
            mCount++;
            onCountAddSuccess();
        } else {
        }
    }

    /**
     * 数量增加成功后,使用者回调
     */
    public void onCountAddSuccess() {
        if (mCount == 1) {
            cancelAllAnim();
            mAnimReduceHint.start();
        } else {
            mAnimFraction = 0;
            invalidate();
        }
    }

    /**
     * 数量减少成功后,使用者回调
     */
    public void onCountDelSuccess() {
        if (mCount == 0) {
            cancelAllAnim();
            mAniDel.start();
        } else {
            mAnimFraction = 0;
            invalidate();
        }
    }

动画的实现

这里会用到两个变量:

    //动画的基准值 动画:减 0~1, 加 1~0 
    // 普通状态下是0
    protected float mAnimFraction;

    //提示语收缩动画 0-1 展开1-0
    //普通模式时,应该是1, 只在 isHintMode true 才有效
    protected float mAnimExpandHintFraction;

依次分析有哪些动画:

Hint动画

主要是圆角矩形的展开、收缩
固定right、bottom,当展开时,不断减少矩形的左起点left坐标值,则整个矩形宽度变大,呈现展开。收缩时相反。
代码:

            //背景
            mHintPaint.setColor(mHintBgColor);
            RectF rectF = new RectF(mLeft + (mWidth - mRadius * 2) * mAnimExpandHintFraction, mTop
                    , mWidth - mCircleWidth, mHeight - mCircleWidth);
            canvas.drawRoundRect(rectF, mHintBgRoundValue, mHintBgRoundValue, mHintPaint);

减按钮动画

看起来是旋转、位移、透明度
那么对于背景的圆圈来说,我们只需要位移、透明度。因为它本身是个圆,就不要旋转了。
代码:

            //动画 mAnimFraction :减 0~1, 加 1~0 ,
            //动画位移Max,
            float animOffsetMax = (mRadius * 2 +mGapBetweenCircle);
            //透明度动画的基准
            int animAlphaMax = 255;
            int animRotateMax = 360;

            //左边
            //背景 圆
            mDelPaint.setAlpha((int) (animAlphaMax * (1 - mAnimFraction)));
            mDelPath.reset();
            //改变圆心的X坐标,实现位移
            mDelPath.addCircle(animOffsetMax * mAnimFraction + mLeft + mRadius, mTop + mRadius, mRadius, Path.Direction.CW);
            canvas.drawPath(mDelPath, mDelPaint);

对于前景的“-”号来说,旋转、位移、透明度都需要做。
这里我们利用canvas.translate() canvas.rotate 做旋转和位移动画,别忘了 canvas.save()canvas.restore()恢复画布的状态。(透明度在上面已经设置过了。)

            //前景 -
            //旋转动画
            canvas.save();
            canvas.translate(animOffsetMax * mAnimFraction + mLeft + mRadius, mTop + mRadius);
            canvas.rotate((int) (animRotateMax * (1 - mAnimFraction)));
            canvas.drawLine(-mRadius / 2, 0,
                    +mRadius / 2, 0,
                    mDelPaint);
            canvas.restore();

数量的动画

看起来也是旋转、位移、透明度。同样是利用canvas.translate() canvas.rotate 做旋转和位移动画。

            //数量
            canvas.save();
            //平移动画
            canvas.translate(mAnimFraction * (mGapBetweenCircle / 2 - mTextPaint.measureText(mCount + "") / 2 + mRadius), 0);
            //旋转动画,旋转中心点,x 是绘图中心,y 是控件中心
            canvas.rotate(360 * mAnimFraction,
                mGapBetweenCircle / 2 + mLeft + mRadius * 2 ,
                    mTop + mRadius);
            //透明度动画
            mTextPaint.setAlpha((int) (255 * (1 - mAnimFraction)));
            //是没有动画的普通写法,x left, y baseLine
            canvas.drawText(mCount + "",  mGapBetweenCircle / 2 - mTextPaint.measureText(mCount + "") / 2 + mLeft + mRadius * 2, mTop + mRadius - (mFontMetrics.top + mFontMetrics.bottom) / 2, mTextPaint);
            canvas.restore();

动画的定义:

动画是在View初始化时就定义好的,执行顺序:

  • 数量增加,0-1时,先收缩Hint(第二层)mAnimReduceHint执行,完毕后执行减按钮(第一层)进入的动画mAnimAdd
  • 数量减少,1-0时,先执行减按钮退出的动画mAniDel,再伸展Hint动画mAnimExpandHint,完毕后,显示hint文字。

代码如下:

        //动画 +
        mAnimAdd = ValueAnimator.ofFloat(1, 0);
        mAnimAdd.addUpdateListener(new ValueAnimator.AnimatorUpdateListener() {
            @Override
            public void onAnimationUpdate(ValueAnimator animation) {
                mAnimFraction = (float) animation.getAnimatedValue();
                invalidate();
            }
        });
        mAnimAdd.setDuration(350);

        //提示语收缩动画 0-1
        mAnimReduceHint = ValueAnimator.ofFloat(0, 1);
        mAnimReduceHint.addUpdateListener(new ValueAnimator.AnimatorUpdateListener() {
            @Override
            public void onAnimationUpdate(ValueAnimator animation) {
                mAnimExpandHintFraction = (float) animation.getAnimatedValue();
                invalidate();
            }
        });
        mAnimReduceHint.addListener(new AnimatorListenerAdapter() {
            @Override
            public void onAnimationEnd(Animator animation) {
                if (mCount == 1) {
                    //然后底色也不显示了
                    isHintMode = false;
                }
                if (mCount == 1) {
                    Log.d(TAG, "现在还是1 开始收缩动画");
                    if (mAnimAdd != null && !mAnimAdd.isRunning()) {
                        mAnimAdd.start();
                    }
                }
            }

            @Override
            public void onAnimationStart(Animator animation) {
                if (mCount == 1) {
                    //先不显示文字了
                    isShowHintText = false;
                }
            }
        });
        mAnimReduceHint.setDuration(350);

        //动画 -
        mAniDel = ValueAnimator.ofFloat(0, 1);
        mAniDel.addUpdateListener(new ValueAnimator.AnimatorUpdateListener() {
            @Override
            public void onAnimationUpdate(ValueAnimator animation) {
                mAnimFraction = (float) animation.getAnimatedValue();
                invalidate();
            }
        });
        //1-0的动画
        mAniDel.addListener(new AnimatorListenerAdapter() {
            @Override
            public void onAnimationEnd(Animator animation) {
                if (mCount == 0) {
                    Log.d(TAG, "现在还是0onAnimationEnd() called with: animation = [" + animation + "]");
                    if (mAnimExpandHint != null && !mAnimExpandHint.isRunning()) {
                        mAnimExpandHint.start();
                    }
                }
            }
        });
        mAniDel.setDuration(350);
        //提示语展开动画
        //分析这个动画,最初是个圆。 就是left 不断减小
        mAnimExpandHint = ValueAnimator.ofFloat(1, 0);
        mAnimExpandHint.addUpdateListener(new ValueAnimator.AnimatorUpdateListener() {
            @Override
            public void onAnimationUpdate(ValueAnimator animation) {
                mAnimExpandHintFraction = (float) animation.getAnimatedValue();
                invalidate();
            }
        });
        mAnimExpandHint.addListener(new AnimatorListenerAdapter() {
            @Override
            public void onAnimationEnd(Animator animation) {
                if (mCount == 0) {
                    isShowHintText = true;
                }
            }
            @Override
            public void onAnimationStart(Animator animation) {
                if (mCount == 0) {
                    isHintMode = true;
                }
            }
        });
        mAnimExpandHint.setDuration(350);

针对复用机制的处理

因为我们的购物车控件肯定会用在列表中,不管你用ListView还是RecyclerView,都会涉及到复用的问题。
复用给我们带来一个麻烦的地方就是,我们要处理好一些属性状态值,否则UI上会有问题。

可以从两处下手处理:

onMeasure

列表复用时,依然会回调onMeasure()方法,所以在这里初始化一些UI显示的参数。
这里顺带将适配wrap_content 的代码也一同贴上:

protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
        int wMode = MeasureSpec.getMode(widthMeasureSpec);
        int wSize = MeasureSpec.getSize(widthMeasureSpec);
        int hMode = MeasureSpec.getMode(heightMeasureSpec);
        int hSize = MeasureSpec.getSize(heightMeasureSpec);
        switch (wMode) {
            case MeasureSpec.EXACTLY:
                break;
            case MeasureSpec.AT_MOST:
                //不超过父控件给的范围内,自由发挥
                int computeSize = (int) (getPaddingLeft() + mRadius * 2 +mGapBetweenCircle + mRadius * 2 + getPaddingRight() + mCircleWidth * 2);
                wSize = computeSize < wSize ? computeSize : wSize;
                break;
            case MeasureSpec.UNSPECIFIED:
                //自由发挥
                computeSize = (int) (getPaddingLeft() + mRadius * 2 + mGapBetweenCircle + mRadius * 2 + getPaddingRight() + mCircleWidth * 2);
                wSize = computeSize;
                break;
        }
        switch (hMode) {
            case MeasureSpec.EXACTLY:
                break;
            case MeasureSpec.AT_MOST:
                int computeSize = (int) (getPaddingTop() + mRadius * 2 + getPaddingBottom() + mCircleWidth * 2);
                hSize = computeSize < hSize ? computeSize : hSize;
                break;
            case MeasureSpec.UNSPECIFIED:
                computeSize = (int) (getPaddingTop() + mRadius * 2 + getPaddingBottom() + mCircleWidth * 2);
                hSize = computeSize;
                break;
        }
        setMeasuredDimension(wSize, hSize);

        //复用时会走这里,所以初始化一些UI显示的参数
        mAnimFraction = 0;
        initHintSettings();
    }
    /**
     * 根据当前count数量 初始化 hint提示语相关变量
     */
    private void initHintSettings() {
        if (mCount == 0) {
            isHintMode = true;
            isShowHintText = true;
            mAnimExpandHintFraction = 0;
        } else {
            isHintMode = false;
            isShowHintText = false;
            mAnimExpandHintFraction = 1;
        }
    }

在改变count时

一般在onBindViewHolder()或者getView()时,都会对本控件重新设置count值,count改变时,当然也是需要根据count进行属性值的调整。
且此时如果View正在做动画,应该停止这些动画。

    /**
     * 设置当前数量
     * @param count
     * @return
     */
    public AnimShopButton setCount(int count) {
        mCount = count;
        //先暂停所有动画
        if (mAnimAdd != null && mAnimAdd.isRunning()) {
            mAnimAdd.cancel();
        }
        if (mAniDel != null && mAniDel.isRunning()) {
            mAniDel.cancel();
        }
        //复用机制的处理
        if (mCount == 0) {
            // 0 不显示 数字和-号
            mAnimFraction = 1;
        } else {
            mAnimFraction = 0;
        }
        initHintSettings();
        return this;
    }

总结

代码传送门:喜欢的话,随手点个star。多谢
https://github.com/mcxtzhang/AnimShopButton

经济上支持我 or 想通过视频看我是怎么实现的:

http://edu.csdn.net/course/detail/3898

我在实现这个控件时,觉得难度相对大的地方在于做动画时,“-”按钮和数量的旋转动画,如何确定正确的坐标值。因为将text绘制的居中本身就有一些注意事项在里面,再涉及到动画,难免蒙圈。需要多计算,多试验

还有就是观察饿了么的效果,将hint区域的动画利用改变RoundRect的宽度去实现。起初没有想到,也是思考了一会如何去做。这是属于分析、拆解动画遇到的问题。

除了绘制以外的重点是:

  • 利用Region监听区域点击事件
  • 复用的列表,如何正确显示UI。
  • 动画次序以及考虑到复用时,在合适的地方取消动画

尽情在项目中使用它吧,有问题随时gayhub给我反馈。

通过sdk工具查看饿了么,它其实是用TextViewImageView组合实现的。另外我十分怀疑它没有封装成控件,因为在列表页和详情页的交互,以及动画居然略有不同, 在详情页,仔细看由0-1时,它右边的 + 按钮的动画居然会闪一下,在列表页却没有,很是不解。

看大神们都有QQ群,
向他们靠齐。
我也建了个QQ搞基交流群:
557266366 。

想看我更多文章:【张旭童的博客】
想来gayhub和我gaygayup:【mcxtzhang Github主页】
代码传送门:喜欢的话,随手点个star。多谢
https://github.com/mcxtzhang/AnimShopButton

共收到 6 条回复
2
jixiaohua · #1 ·

赞,用原生仿饿了么的HTML5页面

30
d_clock · #2 ·

哎哟,不错哦。

3424 1482115434
zxt0601 · #3 ·

#1楼 @jixiaohua 大佬,饿了么的这个不是 native的页面么。
我用sdk工具查看了一下是有TextView、ImageView这种控件在上面的呀。

3424 1482115434
zxt0601 · #4 ·

#2楼 @d_clock 大佬常来哦~

2
jixiaohua · #5 ·

#3楼 @zxt0601 我一直以为是HTML5页面呢,😳

96
mm0089 · #6 ·

臺灣約妹找妓女做愛打炮賴mm0089 堅持讓你花得值得 讓你一次就滿意
薇薇外送茶優質正妹網站:http://www.kiss69lg.com/forum.php?forumlist=1&mobile=2
堅持讓你花得值得 讓你一次就滿意
保證咩咩 熟練多變的技巧 讓你三度回味
堅持誠信經營 絕不會說的天花亂墬 浪費彼此寶貴的時間
精心安排 保證約好的時間 20分鐘內火速到達
絕無強迫消費‧購買點數‧匯款‧ATM轉帳‧都是見到本人 滿意在消費
優咩咩都是經過篩選 絕無地雷 請放心光臨體驗享受
㊣平價錢又安心 熟客 新客都享有優惠
薇薇茶坊營業時間:中午12點-凌晨四點

外送地區:臺北新北林口龜山新竹臺中彰化南投高雄臺南
妹妹類型:學生 OL 巨乳 蘿莉 人妻 技術茶 空姐小模 AV女優 婚紗助理等
服務內容:全套服務
約妹流程:賴上先預約然後準時給房號在房間等妹到
約會地點:你自己選擇的旅館or熟客可送住家
消費方式:看到妹喜歡在當場現金交易 不喜歡可換三次

北部:一節6000內立減500送1000優惠券
一節7000 -8000第二節半價 買三節送一節
一節9000-10000 立減1000-2000送2次半價
一節11000-15000 立減3000-5000送3次半價
一節15000-30000 立減5000-8000 送一年半價

中南部:一節4000-5000 二節半價
一節6000-7000 買兩節送一節
一節7000-10000 買兩節送兩節
一節11000-15000 立減3000-5000送2次半價
一節15000-30000立減5000-8000送一年半價
約小姐部落格看照網址:https://www.cssanyu.org/bbs2/forum.php?mod=viewthread&tid=333726&extra=
約小姐部落格看照https://www.photostore.me/mm0089/?list=images&sort=date_desc&page=4&seek=ursjV
雙北桃園林口龜山新竹看照約妹網址:http://www.kiss69lg.com/forum.php?mod=forumdisplay&fid=143
臺中彰化南投看照約妹網址:http://www.kiss69lg.com/forum.php?mod=forumdisplay&fid=145
高雄臺南看照約妹網址:http://www.kiss69lg.com/forum.php?mod=forumdisplay&fid=147
安全旅館便宜經濟實惠旅館推薦:http://www.kiss69lg.com/forum.php?mod=forumdisplay&fid=182
色情圖片露點照片網址:http://www.kiss69lg.com/forum.php?mod=forumdisplay&fid=177
成人小說網址http://www.kiss69lg.com/forum.php?mod=forumdisplay&fid=163
台中台北外送茶莊、茶坊推薦|優質台北、高雄外約茶妹任你選南投外送茶/全套叫小姐彰化外送茶
草屯外送茶大台中外送茶,外約美女,薇薇外送茶賴mm0089外約服務網,台中一夜情 .台灣出差旅館叫小姐line:mm0089
台北外送茶/台中外送茶/高雄外送茶/台南外送茶/新竹外送茶/彰化外送茶/南投外送茶/薇薇外送茶賴mm0089大台灣台北台中高雄台南新竹地區喝茶服務外送茶薇薇外送茶賴mm0089/鼓山區看照約妹薇薇外送茶賴mm0089前鎮區美腿茶/三民區火辣茶/新興區惹火嫵媚茶/左營區MT外送... 外送/台北清涼茶莊/更多優質妹妹/台北外送茶莊/高雄外送茶/新竹外送茶/洗澡/愛愛薇薇外送茶賴mm0089/口交旅館酒店外送小姐/上門服務/薇薇外送茶賴mm0089茶魚分享/大台北外送茶坊/大台中外送茶坊/高雄外送茶/援交妹網站台灣叫小姐俱樂部,薇薇外送茶賴mm0089台北叫小姐,薇薇外送茶賴mm0089西門町外送服務 板橋外送茶高雄約妹外約茶莊薇薇外送茶賴mm0089/夜市附近叫小姐,85大樓叫小姐 高雄叫小姐按摩外送茶西?町找小姐薇薇外送茶賴mm0089林森北找茶喝/台北?正妹/台?援交找薇薇外送茶賴mm0089台北叫小姐台中旅館叫小姐高雄旅遊找妹兼職美女茶外送看照約妹好茶外送到家西?町找小姐|林森北找茶喝/台北?正妹/台?援交找薇薇外送茶賴mm0089
#台灣汽車旅館找小姐 #商旅找小姐薇薇外送茶賴mm0089 #星級酒店找小姐
#旅館找小姐 #商旅找小姐薇薇外送茶賴mm0089 #星級酒店找小姐 #旅館找小姐
#飯店找小姐 #住家找小姐薇薇外送茶賴mm0089 #賓館找小姐 #台灣出差旅遊外約 #台灣出差旅遊找小姐
#酒店外約叫茶 #台灣汽車旅館外約叫茶 薇薇外送茶賴mm0089#商旅外約叫茶 #星級酒店外約叫茶 #旅館外約叫茶 #飯店外約叫茶
#住家外約叫茶 #賓館外約叫茶 #台灣出差旅遊外約叫茶 薇薇外送茶賴mm0089#台灣出差旅遊外約叫茶 +薇薇外送茶賴mm0089#
台中外送茶 #台北外送茶 #新竹外送茶薇薇外送茶賴mm0089 #高雄外送茶 #台南外送茶 #彰化外送茶 #南投外送茶 #台中外約
#台北外約 #高雄外約薇薇外送茶賴mm0089 #新竹外約 #台南外約 #彰化外約 #南投外約 #台灣外送茶 #外送茶 #情愛全台外送茶看照約妹叫小姐
#外送茶不戴套 +薇薇外送茶賴mm0089#板橋外約 #三重外約 #永和外約 #中和外約 #汐止外約 #新莊外約 #土城外約
#新店外約 #蘆洲外約 #五股外約 #泰山外約 #淡水外約薇薇外送茶賴mm0089 #八里外約 #林口外約 #龜山外約 #台中外約 #高雄外約
#台北外約薇薇外送茶賴mm0089 #本土外約 #外約台妹 #中正外約 #大同外約 #松山外約+薇薇外送茶賴mm0089 #板橋外送茶 #板橋外約
#大安外約 #萬華外約 #信義外約薇薇外送茶賴mm0089 #士林外約 #北投外約 #內湖外約 #南港外約 #文山外約 #新竹外約 #台南外約
#西屯外約 #南屯外約 薇薇外送茶賴mm0089#北屯外約 #逢甲外約 #大里外約 #大雅外約 #七其外約 #東海外約 #烏日外約 #太平外約
#豐原外約薇薇外送茶賴mm0089 #沙鹿外約 薇薇外送茶賴mm0089#逢甲茶莊 #逢甲全套
#薇薇外送茶賴mm0089逢甲外約 #逢甲外送茶 #逢甲叫小姐 #逢甲打砲 屏東汽車旅館叫小姐.薇薇外送茶賴mm0089屏東找茶,屏東外送舒壓按摩.屏東護膚全套外約.屏東找妹薇薇外送茶賴mm0089.屏東找小姐.屏東汽車旅館叫妹妹服務薇薇外送茶賴mm0089.屏東叫小姐.妹妹服務找歡樂.薇薇外送茶賴mm0089屏東優質外送茶莊.屏東出差旅遊約妹 .薇薇外送茶賴mm0089屏東正妹論壇.屏東約情人.薇薇外送茶賴mm0089屏東找女人兼職妹.推薦屏東茶莊##屏東外約妹妹價位#屏東找小姐.薇薇外送茶賴mm0089屏東全套外送.屏東辣妹薇薇外送茶賴mm0089.屏東找學生妹.人妻
#逢甲茶訊 #逢甲援交 #逢甲找女人薇薇外送茶賴mm0089 #逢甲魚訊 #逢甲炮神器 #逢甲紓壓 #逢甲性愛服務 #逢甲鐘點情人 #
太平全套 #薇薇外送茶賴mm0089大里全套 #沙鹿全套 #豐原全套 #大雅全套 #烏日全套薇薇外送茶賴mm0089 #台中車站應召 #台中南屯約妹
#台中西屯叫小姐 #台中逢甲外送 #台中勤美約妹薇薇外送茶賴mm0089 #台中車站叫小姐 #台中北屯應召 #台中南屯叫雞 #台中車站叫妹
#台中北區叫小姐薇薇外送茶賴mm0089 #台中應召 #台中車站叫雞 #台中車站茶莊 #台中西屯外送薇薇外送茶賴mm0089 #台中逢甲約砲 #台中北屯叫妹 #台中市區應召桃園半套店薇薇外送茶賴mm0089 ,#桃園半套價錢 #桃園找援,#桃園聯天室找援,#桃園西門找援桃園半套店薇薇外送茶賴mm0089 ,#桃園半套價錢 台中一夜情,台中??,台中全套基隆叫小姐電話 #基隆叫小姐 #基隆飯店叫小姐 #基隆旅館叫小姐薇薇外送茶賴mm0089 #基隆找女人 #基隆找妹 #基隆出差叫小姐 #基隆打炮薇薇外送茶賴mm0089 #基隆看照約妹#桃園茶莊心得,#桃園茶莊ptt,#桃園桑拿薇薇外送茶賴mm0089 ,#桃園桑拿浴,#桃園桑拿網
#桃園桑拿論壇薇薇外送茶賴mm0089 ,#桃園桑拿澳門,#桃園桑拿168,#桃園半套店薇薇外送茶賴mm0089 ,#桃園半套價錢
台北外送茶/高雄鐘點情人外約,台北旅館叫小姐,台北鐘點情人,台灣一夜情,高雄一夜情,台中一夜情,台北一夜情,台北美女外約,台中美女外約,高雄美女外約,高雄茶莊,台中茶莊,台北茶莊,台北叫小姐,台中叫小姐,高雄叫小姐,高雄外約/外約/援交妹,吃魚喝茶論壇,大家來找茶,PLUS,伊利,微克成人網/女優GoGoGo/淘A片/一刀未剪/免費成人影音薇薇外送茶賴mm0089 /交換網站/交友網站/性愛成人網/一葉晴成人貼片/成人極品情色站/癡漢線上免費A片/台灣明星淫片流出露比線上免費A片/伊莉成人論壇/◆免費線上A片◆/寶貝一夜情聊天室/免費A片頻道/無名成人網/成人網/台北情色聯盟/天天幹貼圖/洪爺色情網/台灣噴精成人網/十八小妹自拍美少女自拍貼圖老婆自拍貼圖/色色女孩情色總站/A圖情色交流/666人氣貼圖/插插穴排行/69Kiss電影排行/薇薇外送茶賴mm0089 彩虹頻道/后宮電影院/中文搜性網/酷站排行入口/上我人妻/成人龍虎豹/干爹情色排行/十七歲少女/台灣1歲/104寫真銀行/插插穴排行/熱酷美眉網/台灣性樂園/只有貼圖/波波美女網/交換連結eyny,玩美情人,男人幫,高雄女外約,高雄外送,高雄賓館叫小姐,高雄飯店叫小姐,高雄鐘點情人外約,台北旅館叫小姐,彰化外送茶,台灣兼職美女外送,薇薇外送茶賴mm0089 台北兼職美女外約,台中外送茶坊,高雄外送茶坊,台北外送茶坊陸妹價格,檳榔西施清涼秀,台功援學生兼差 msn,援交妹,24h 台北私兼,CLUB,台南指壓 3k,高雄酒店經紀,台中學生兼差msn,台北 夜店 舞廳 酒吧 制服便服,中年夫妻聯誼,高雄媛交,台南茶妹,台北指油壓留言板,情趣精品,大台南一夜情人外約\俱樂部,台北賓館叫小姐,高雄原味貼身衣物買賣,夢時代購物中心,台北推拿中醫,0204一夜崤◆隆f聊天室,熟女圖,討論區,台南喝茶的店哪好,網路購物,台中理容按摩,台南陪唱,台北下\午茶 blog,高雄 砲友,台中好茶討論區,高雄茶店news,高雄按摩個人工作室,旅遊,台南24h台南24h餐廳,台北茶訊交流msn,卡債,台中旅館外叫服務,台中 spa油壓男按摩小姐服務/漁會玩美情人遊戲成人論壇 台北吃魚喝茶留言板外送/台北一夜情重點情/台北旅館飯店找服務叫小姐/找女人全套服務加按摩指油壓成人夜遊魚訊交流論壇區/台北應召站/伊莉喝茶/第一手論壇/外約愛愛/外約電話/外約高檔茶到府服務/伊莉plus28/成人性愛慾茶園 性交易/正妹外送服務/找茶論壇薇薇外送茶賴mm0089 /找茶討論區/台灣外送GTO/台灣樂緣外送茶/兩性論妹板橋外送酒店 北投泡溫泉三溫暖趙小姐/援交妹網站論壇/FB交友網站/UT天室交友一夜情炮友/台北喝茶買三送一接多買多送純情動感兼職妹/華僑台北旅遊出差消伴遊找女人茶/薇薇外送茶賴mm0089 極品俱樂部嚴選絕色經典,第一手娛樂論壇/卡提諾/玩美情人/吃魚喝茶網 薇薇外送茶賴mm0089 伊漁網/Plus論壇/台灣樂緣/小女人論壇/台灣論壇/微風論壇/伊莉論壇/禁地論壇/維克斯論壇/捷克論/男人幫論壇/大眾論壇/竹北旅館飯店找女人按摩舒壓叫小姐3p服務 愛情公寓論壇 交友/ 愛情/戀愛/貓都論壇/賽斯論壇/104論壇/九州娛樂論壇/櫻雪論壇/2B級/台灣、送茶坊台北外送茶坊,台中外送茶,高雄外送茶,美女外約服務/台中/高雄/新竹/彰化/莊極品俱樂部嚴選絕色經典,成人性愛慾茶園,找茶討論區,催情藥,唯美貼圖,成人論壇,網絡報稅,線上遊戲,高檔平價好茶,淫照聊天是尋夢園美女外送,情趣用品八大行業指油壓全套,暑假打工,網站設計,中國合夥人,鋼鐵俠3,HTC,蝴蝶機,變裝遊戲,茶,motel,hotel,性感絲襪,A片下載,AV女優,第一手論壇,貓都,卡提諾,喝茶,完美情人,BJ論壇,小女人論壇,卡提諾論壇,台灣論壇三溫暖中陪酒ktv,台北全套護膚個人工作室,尋找台南援交auty 美容美體 SPA沙龍,台北外約茶棧,台中越南餐廳,台南應召站,台北外送 3k,高雄下午茶外,台中三溫暖全套,台南全套油壓泰國,台南半套店1600元,高雄推拿指壓,小姐,台北夜生活 pub,台北單身聯誼,兼差,台北交換伴侶,台北一夜情,高雄聊天網,高雄美女兼職,台南車站美食,8000mile,台北一夜情緣俱樂部,高雄應徵\酒店酒店上班,高雄成人視訊聊天室,台南兼職找利菁,高雄24h到府指油壓,女兼職,酒店兼職\,壽山,台南茶訊茶資薇薇外送茶賴mm0089 ,台南陪酒小姐,高雄絲襪美腿高跟鞋,夫妻聯誼部落格,薇薇外送茶賴mm0089 台南小野貓檳榔西施外送時被下藥拍照,自拍女老師,找台中援妹地點,台北大陸妹價格,檳榔西施清涼秀,台功援學生兼差 msn,援交妹,24h 台北私兼,CLUB,台南指壓 3k,高雄酒店經紀,台中學生兼差msn,台北 夜店 舞廳 酒吧 制服便服,中年夫妻聯誼,高雄媛交,台南茶妹,台北指油壓留言板,情趣精品,大台南一夜情人外約\俱樂部,台北賓館叫小姐,高雄原味貼身衣物買賣,夢時代購物中心,台北推拿中醫,0204一夜崤◆隆f聊天室,熟女圖,討論區,台南喝茶的店哪好,網福祿猴林千又 吳宗憲 2015 黑豹旗 王大陸 徐太宇 登革熱 波多野結衣 金鐘獎 蔡英文 洪秀柱 蛇精男 靈異 鬼故事 柯文哲 柯P 大家來說鬼 綜藝玩很大 時尚脈動 賈靜雯 陳佩琪 氣象 反課綱 2015星光大賞 宅男女神 愛爾麗 泛舟哥 張吉吟 ET看電影 八仙 塵爆 楊子晴 范冰冰 安心亞 陳泱瑾 Grace ISIS 林書豪

需要 Sign In 后方可回复, 如果你还没有账号请点击这里 Sign Up