仿360手机助手下载按钮

本篇文章已授权微信公众号guolin_blog(郭霖)独家发布

最近在学习android的高级view的绘制,再结合值动画的数据上的改变,自己撸了个360手机助手的下载按钮。先看下原版的360手机助手的下载按钮是长啥样子吧:

360下载按钮效果图.gif

再来看看自己demo吧,你们尽情的吐槽吧,哈哈:


360downSimple.gif

里面的细节问题还会不断地更改的,gif的动态图是有些快的,这是因为简书要求gif的大小了,这个也冒得办法啊 。所以想看真是效果的筒子们,可以去看demo哈。

完善后的效果图.gif

细心的朋友可能发现loading状态下左边几个运动圆的最高点和最低点都越界了,这是因为在规定正弦函数的最高点时没考虑圆的半径的长度,因此近两天做了点修改了,效果图如下:

修改loading状态下的运动点最高点和最低点.gif

细节分析步骤图:
咱们的整个过程可以分为这么几个状态,在这里我用枚举类进行了归纳:

 public enum Status {
        Normal, Start, Pre, Expand, Load, Complete;
 }

Normal(还没进行开始的状态,也就是我们的默认状态,也就是我们还没执行onTouch的时候了):

normal状态.png

Start(点击onTouch改变为该状态):

@Override
public boolean onTouchEvent(MotionEvent event) {
    final int action = MotionEventCompat.getActionMasked(event);
    //抬起的时候去改变status
    if (action == MotionEvent.ACTION_UP) {
        status = Status.Start;
        startAnimation(collectAnimator);
    }
    return true;
}

那咱们再来看看collectAnimator做了些什么呢:

collectAnimator = new Animation() {
    @Override
    protected void applyTransformation(float interpolatedTime, Transformation t) {
        currentLength = (int) (width - width * interpolatedTime);
        if (currentLength <= height) {
            currentLength = height;
            clearAnimation();
            status = Status.Pre;
            angleAnimator.start();
        }
        invalidate();
    }
};
collectAnimator.setInterpolator(new LinearInterpolator());
collectAnimator.setDuration(collectSpeed);

其实核心的就是在这个过程中改变了全局变量currentLength而已,此时我们回到onDraw里面吧,看看在Start状态下currentLength都做了些什么:

@RequiresApi(api = Build.VERSION_CODES.LOLLIPOP)
@Override
protected void onDraw(Canvas canvas) {
    super.onDraw(canvas);
    if (status == Status.Normal || status == Status.Start) {
        float start = (float) (width * 1.0 / 2 - currentLength * 1.0 / 2);
        canvas.drawRoundRect(start, 0, (float) (width * 1.0 / 2 + currentLength * 1.0 / 2), height, 90, 90, bgPaint);
        Paint.FontMetrics fontMetrics = textPaint.getFontMetrics();
        float allHeight = fontMetrics.descent - fontMetrics.ascent;
        if (status == Status.Normal) {
            canvas.drawText("下载", (float) (width * 1.0 / 2), (float) (height * 1.0 / 2 - allHeight / 2 - fontMetrics.ascent), textPaint);
        }
    } else if (status == Status.Pre) {
        canvas.drawCircle((float) (width * 1.0 / 2), (float) (height * 1.0 / 2), (float) (height * 1.0 / 2), bgPaint);
        canvas.save();
        canvas.rotate(angle, (float) (width * 1.0 / 2), (float) (height * 1.0 / 2));
        canvas.drawCircle((float) (width * 1.0 / 2), (float) (height * 1.0 / 2), 25, textPaint);
        canvas.drawCircle((float) (width * 1.0 / 2), (float) (height * 1.0 / 2) - 24, 15, textPaint);
        canvas.drawCircle((float) (width * 1.0 / 2 - 22), (float) ((height * 1.0 / 2) + 18 * 0.866), 15, textPaint);
        canvas.drawCircle((float) (width * 1.0 / 2 + 22), (float) ((height * 1.0 / 2) + 18 * 0.866), 15, textPaint);
        canvas.restore();
    } else if (status == Status.Expand) {
        float start = (float) (width * 1.0 / 2 - currentLength * 1.0 / 2);
        canvas.drawRoundRect(start, 0, (float) (width * 1.0 / 2 + currentLength * 1.0 / 2), height, 90, 90, bgPaint);

        canvas.save();
        canvas.translate(translateX, 0);
        canvas.drawCircle((float) (width * 1.0 / 2), (float) (height * 1.0 / 2), 25, textPaint);
        canvas.drawCircle((float) (width * 1.0 / 2), (float) (height * 1.0 / 2) - 24, 15, textPaint);
        canvas.drawCircle((float) (width * 1.0 / 2 - 22), (float) ((height * 1.0 / 2) + 18 * 0.866), 15, textPaint);
        canvas.drawCircle((float) (width * 1.0 / 2 + 22), (float) ((height * 1.0 / 2) + 18 * 0.866), 15, textPaint);
        canvas.restore();
    } else if (status == Status.Load || status == Status.Complete) {

        float start = (float) (width * 1.0 / 2 - currentLength * 1.0 / 2);
        bgPaint.setColor(progressColor);
        canvas.drawRoundRect(start, 0, (float) (width * 1.0 / 2 + currentLength * 1.0 / 2), height, 90, 90, bgPaint);
        if (progress != 100) {
            //画中间的几个loading的点的情况哈
            if (fourMovePoint[0].isDraw)
                canvas.drawCircle(fourMovePoint[0].moveX, fourMovePoint[0].moveY, fourMovePoint[0].radius, textPaint);
            if (fourMovePoint[1].isDraw)
                canvas.drawCircle(fourMovePoint[1].moveX, fourMovePoint[1].moveY, fourMovePoint[1].radius, textPaint);
            if (fourMovePoint[2].isDraw)
                canvas.drawCircle(fourMovePoint[2].moveX, fourMovePoint[2].moveY, fourMovePoint[2].radius, textPaint);
            if (fourMovePoint[3].isDraw)
                canvas.drawCircle(fourMovePoint[3].moveX, fourMovePoint[3].moveY, fourMovePoint[3].radius, textPaint);
        }

        float progressRight = (float) (progress * width * 1.0 / 100);
        //在最上面画进度
        bgPaint.setColor(bgColor);

        canvas.save();
        canvas.clipRect(0, 0, progressRight, height);
        canvas.drawRoundRect(start, 0, (float) (width * 1.0 / 2 + currentLength * 1.0 / 2), height, 90, 90, bgPaint);
        canvas.restore();

        if (progress != 100) {
            bgPaint.setColor(bgColor);
            canvas.drawCircle((float) (width - height * 1.0 / 2), (float) (height * 1.0 / 2), (float) (height * 1.0 / 2), bgPaint);
            canvas.save();
            canvas.rotate(loadAngle, (float) (width - height * 1.0 / 2), (float) (height * 1.0 / 2));
              canvas.drawCircle(width - height + 25, getCircleY(width - height + 25), 5, textPaint);
            canvas.drawCircle(width - height + 40, getCircleY(width - height + 40), 7, textPaint);
            canvas.drawCircle(width - height + 60, getCircleY(width - height + 60), 9, textPaint);
            canvas.drawCircle(width - height + 90, getCircleY(width - height + 90), 11, textPaint);
            canvas.restore();
        }
        //中间的进度文字
        Paint.FontMetrics fontMetrics = textPaint.getFontMetrics();
        float allHeight = fontMetrics.descent - fontMetrics.ascent;
        canvas.drawText(progress + "%", (float) (width * 1.0 / 2), (float) (height * 1.0 / 2 - allHeight / 2 - fontMetrics.ascent), textPaint);
    }
}

为了便于我们分析每一个状态,我们就看下每个状态下的绘制动作吧:

if (status == Status.Normal || status == Status.Start) {
    float start = (float) (width * 1.0 / 2 - currentLength * 1.0 / 2);
    canvas.drawRoundRect(start, 0, (float) (width * 1.0 / 2 + currentLength * 1.0 / 2), height, 90, 90, bgPaint);
    Paint.FontMetrics fontMetrics = textPaint.getFontMetrics();
    float allHeight = fontMetrics.descent - fontMetrics.ascent;
    if (status == Status.Normal) {
        canvas.drawText("下载", (float) (width * 1.0 / 2), (float) (height * 1.0 / 2 - allHeight / 2 - fontMetrics.ascent), textPaint);
    }
}

大家看到变量currentLength了没,其实这里就是去改变背景的right坐标,正好上面动画里面也是从width减小的一个值,那么此时的动画大家脑海里能想象得出来了吧:

start效果图.gif

Start状态结束都就是进入到Pre状态了:
上面collectAnimator动画结束后启动的动画是:angleAnimator了,
我们再去看看该动画都做了些啥:

angleAnimator = ValueAnimator.ofFloat(0, 1);
angleAnimator.addUpdateListener(new ValueAnimator.AnimatorUpdateListener() {
    @Override
    public void onAnimationUpdate(ValueAnimator animation) {
        angle += 10;
        invalidate();
    }
});

改变的还是全局的变量angle,再来看看该变量在onDraw方法里面都做了些啥吧:

else if (status == Status.Pre) {
    canvas.drawCircle((float) (width * 1.0 / 2), (float) (height * 1.0 / 2), (float) (height * 1.0 / 2), bgPaint);
    canvas.save();
    canvas.rotate(angle, (float) (width * 1.0 / 2), (float) (height * 1.0 / 2));
    canvas.drawCircle((float) (width * 1.0 / 2), (float) (height * 1.0 / 2), 25, textPaint);
    canvas.drawCircle((float) (width * 1.0 / 2), (float) (height * 1.0 / 2) - 24, 15, textPaint);
    canvas.drawCircle((float) (width * 1.0 / 2 - 22), (float) ((height * 1.0 / 2) + 18 * 0.866), 15, textPaint);
    canvas.drawCircle((float) (width * 1.0 / 2 + 22), (float) ((height * 1.0 / 2) + 18 * 0.866), 15, textPaint);
    canvas.restore();
} 

画了几个圆,然后通过上面的angle变量来旋转canvas,而且几个圆的圆心都与view的中心点有关,因此大家从示例图中应该看出来了:

pre效果图.gif

pre状态结束后,就是Expand状态了,大家可以看pre状态下动画结束的代码:

angleAnimator.addListener(new Animator.AnimatorListener() {
    @Override
    public void onAnimationStart(Animator animation) {

    }

    @Override
    public void onAnimationEnd(Animator animation) {
        status = Status.Expand;
        angleAnimator.cancel();
        startAnimation(tranlateAnimation);
    }

    @Override
    public void onAnimationCancel(Animator animation) {

    }

    @Override
    public void onAnimationRepeat(Animator animation) {

    }
});

可以看出下一个动画tranlateAnimation了,还是一样定位到该动画的代码吧,看看都做了些啥:

tranlateAnimation = new Animation() {
    @Override
    protected void applyTransformation(float interpolatedTime, Transformation t) {
        currentLength = (int) (height + (width - height) * interpolatedTime);
        translateX = (float) ((width * 1.0 / 2 - height * 1.0 / 2) * interpolatedTime);
        invalidate();
    }
};

可以看出此时改变的全局变量有两个:currentLengthtranslateX,想必大家知道currentLength是什么作用了吧,下面就来看看onDraw吧:

else if (status == Status.Expand) {
    float start = (float) (width * 1.0 / 2 - currentLength * 1.0 / 2);
    canvas.drawRoundRect(start, 0, (float) (width * 1.0 / 2 + currentLength * 1.0 / 2), height, 90, 90, bgPaint);

    canvas.save();
    canvas.translate(translateX, 0);
    canvas.drawCircle((float) (width * 1.0 / 2), (float) (height * 1.0 / 2), 25, textPaint);
    canvas.drawCircle((float) (width * 1.0 / 2), (float) (height * 1.0 / 2) - 24, 15, textPaint);
    canvas.drawCircle((float) (width * 1.0 / 2 - 22), (float) ((height * 1.0 / 2) + 18 * 0.866), 15, textPaint);
    canvas.drawCircle((float) (width * 1.0 / 2 + 22), (float) ((height * 1.0 / 2) + 18 * 0.866), 15, textPaint);
    canvas.restore();
}

一个是改变背景的right坐标,再个就是canvas.translate几个中心点的圆了:

expand效果图.gif

expand状态结束后就是正式进入到下载状态了,这里的枚举我定义是Load,
看下expand结束的动画代码吧:

tranlateAnimation.setAnimationListener(new Animation.AnimationListener() {
    @Override
    public void onAnimationStart(Animation animation) {
    }

    @Override
    public void onAnimationEnd(Animation animation) {
        clearAnimation();
        status = Status.Load;
        clearAnimation();
        loadRotateAnimation.start();
        movePointAnimation.start();
    }

    @Override
    public void onAnimationRepeat(Animation animation) {

    }
});

大家可以看到该处有两个动画的启动了(loadRotateAnimation.start()movePointAnimation.start()),说明此处有两个动画在同时执行罢了,先来看loadRotateAnimation动画里面都做了些啥吧:

loadRotateAnimation = ValueAnimator.ofFloat(0, 1);
loadRotateAnimation.addUpdateListener(new ValueAnimator.AnimatorUpdateListener() {
    @Override
    public void onAnimationUpdate(ValueAnimator animation) {
        loadAngle += rightLoadingSpeed;
        if (loadAngle > 360) {
            loadAngle = loadAngle - 360;
        }
        invalidate();
    }
});
loadRotateAnimation.setDuration(Integer.MAX_VALUE);

还是一个角度改变的动画啊,那就看看loadAngle是改变谁的动画吧,还是照常我们进入到onDraw方法吧:

if (progress != 100) {
    bgPaint.setColor(bgColor);
    canvas.drawCircle((float) (width - height * 1.0 / 2), (float) (height * 1.0 / 2), (float) (height * 1.0 / 2), bgPaint);
    canvas.save();
    canvas.rotate(loadAngle, (float) (width - height * 1.0 / 2), (float) (height * 1.0 / 2));
    canvas.drawCircle(width - height + 25, getCircleY(width - height + 25), 5, textPaint);
    canvas.drawCircle(width - height + 40, getCircleY(width - height + 40), 7, textPaint);
    canvas.drawCircle(width - height + 60, getCircleY(width - height + 60), 9, textPaint);
    canvas.drawCircle(width - height + 90, getCircleY(width - height + 90), 11, textPaint);
    canvas.restore();
}

还是一个圆的旋转啊,其实这几个点是有规律去绘制的,他们几个圆心应该是内圆的弧度上的,并且半径是依次增大的。这里调了getCircleY()方法,该方法就是算圆弧上几个点的y坐标。

/**
 * 根据x坐标算出圆的y坐标
 *
 * @param cx:点的圆心x坐标
 * @return
 */
private float getCircleY(float cx) {
    float cy = (float) (height * 1.0 / 2 - Math.sqrt((height * 1.0 / 2 - dp2px(7)) * (height * 1.0 / 2 - dp2px(7)) - ((width - height * 1.0 / 2) - cx) * ((width - height * 1.0 / 2) - cx)));
    return cy;
}

这里看似方法很复杂,其实就是初中定义圆的方程式:(x-cx)2+(y-cy)2=r^2

下面再来看看movePointAnimation动画都做了些啥吧:

fourMovePoint[0] = new MovePoint(dp2px(4), (float) ((width - height / 2) * 0.88), 0);
fourMovePoint[1] = new MovePoint(dp2px(3), (float) ((width - height / 2) * 0.85), 0);
fourMovePoint[2] = new MovePoint(dp2px(2), (float) ((width - height / 2) * 0.80), 0);
fourMovePoint[3] = new MovePoint(dp2px(5), (float) ((width - height / 2) * 0.75), 0);

movePointAnimation = ValueAnimator.ofFloat(0, 1);
movePointAnimation.setRepeatCount(ValueAnimator.INFINITE);
movePointAnimation.setInterpolator(new LinearInterpolator());
movePointAnimation.setDuration(leftLoadingSpeed);
movePointAnimation.addUpdateListener(new ValueAnimator.AnimatorUpdateListener() {
    @Override
    public void onAnimationUpdate(ValueAnimator animation) {
        float value = animation.getAnimatedFraction();
        fourMovePoint[0].moveX = fourMovePoint[0].startX - fourMovePoint[0].startX * value;
        if (fourMovePoint[0].moveX <= height / 2) {
            fourMovePoint[0].isDraw = false;
        }
        fourMovePoint[1].moveX = fourMovePoint[1].startX - fourMovePoint[0].startX * value;
        if (fourMovePoint[1].moveX <= height / 2) {
            fourMovePoint[1].isDraw = false;
        }
        fourMovePoint[2].moveX = fourMovePoint[2].startX - fourMovePoint[0].startX * value;
        if (fourMovePoint[2].moveX <= height / 2) {
            fourMovePoint[2].isDraw = false;
        }
        fourMovePoint[3].moveX = fourMovePoint[3].startX - fourMovePoint[0].startX * value;
        if (fourMovePoint[3].moveX <= height / 2) {
            fourMovePoint[3].isDraw = false;
        }
        fourMovePoint[0].moveY = drawMovePoints(fourMovePoint[0].moveX);
        fourMovePoint[1].moveY = drawMovePoints(fourMovePoint[1].moveX);
        fourMovePoint[2].moveY = drawMovePoints(fourMovePoint[2].moveX);
        fourMovePoint[3].moveY = drawMovePoints(fourMovePoint[3].moveX);
        Log.d("TAG", "fourMovePoint[0].moveX:" + fourMovePoint[0].moveX + ",fourMovePoint[0].moveY:" + fourMovePoint[0].moveY);
    }
});

movePointAnimation.addListener(new Animator.AnimatorListener() {
    @Override
    public void onAnimationStart(Animator animation) {
        fourMovePoint[3].isDraw = true;
        fourMovePoint[2].isDraw = true;
        fourMovePoint[1].isDraw = true;
        fourMovePoint[0].isDraw = true;
    }

    @Override
    public void onAnimationEnd(Animator animation) {

    }

    @Override
    public void onAnimationCancel(Animator animation) {

    }

    @Override
    public void onAnimationRepeat(Animator animation) {
        fourMovePoint[3].isDraw = true;
        fourMovePoint[2].isDraw = true;
        fourMovePoint[1].isDraw = true;
        fourMovePoint[0].isDraw = true;
    }
});

这里首先定义了四个MovePoint,分别定义了他们的半径,圆心,然后在该动画里面不断地改变四个point的圆心,其实这里核心就是如何求出四个点运行的轨迹了,把轨迹弄出来一切就都呈现出来了,可以看看该动画的onAnimationUpdate方法里面调用的drawMovePoints方法:

/**
 * 这里是在load情况下获取几个点运动的轨迹数学函数
 *
 * @param moveX
 * @return
 */
private float drawMovePoints(float moveX) {
    float moveY = (float) (height / 2 + (height / 2 - fourMovePoint[3].radius) * Math.sin(4 * Math.PI * moveX / (width - height) + height / 2));
    return moveY;
}

这里就是一个数学里面经常用的正弦函数了,求出周期、x轴上的偏移量、y轴上的便宜量、顶点,还有一个注意点,该处求顶点的时候,需要减去这几个圆中的最大半径,之前我就是没注意到这点,最后出来的轨迹就是一个圆会跑到view的外面了。效果图如下:

load效果图.gif

最后一个状态就是Complete了,也就是当前的进度到了100,可见代码:

 /**
     * 进度改变的方法
     *
     * @param progress(当前进度)
     */
public void setProgress(int progress) {
    if (status != Status.Load) {
        throw new RuntimeException("your status is not loading");
    }

    if (this.progress == progress) {
        return;
    }
    this.progress = progress;
    if (onProgressUpdateListener != null) {
        onProgressUpdateListener.onChange(this.progress);
    }
    invalidate();
    if (progress == 100) {
        status = Status.Complete;
        this.stop = false;
        clearAnimation();
        loadRotateAnimation.cancel();
        movePointAnimation.cancel();
    }
}

这里要做的就是改变状态,停止一切动画了,到此代码的讲解就到这里了,赶快start起来吧。

属性也没怎么整理,就抽取出了一些比较常用的几个了:

屏幕快照 2017-04-01 14.21.37.png

代码使用:

 /**
 * 进度改变的方法
 * @param progress
 */
public void setProgress(int progress) {
    if (status != Status.Load) {
        throw new RuntimeException("your status is not loading");
    }

    if (this.progress == progress) {
        return;
    }
    this.progress = progress;
    if (onProgressUpdateListener != null) {
        onProgressUpdateListener.onChange(this.progress);
    }
    invalidate();
    if (progress == 100) {
        status = Status.Complete;
        this.stop = false;
        clearAnimation();
        loadRotateAnimation.cancel();
        movePointAnimation.cancel();
    }
}

/**
 * 暂停或继续的方法
 *
 * @param stop(true:表示暂停,false:继续)
 */
public void setStop(boolean stop) {
    if (this.stop == stop) {
        return;
    }
    this.stop = stop;
    if (stop) {
        loadRotateAnimation.cancel();
        movePointAnimation.cancel();
    } else {
        loadRotateAnimation.start();
        movePointAnimation.start();
    }
}

/**
 *设置状态的方法
 * @param status(Down360Loading.Status.Normal:直接取消的操作)
 */
public void setStatus(Status status) {
    if (this.status == status) {
        return;
    }
    this.status = status;
    if (this.status == Status.Normal) {
        progress = 0;
        this.stop = false;
        clearAnimation();
        loadRotateAnimation.cancel();
        movePointAnimation.cancel();
    }
    invalidate();
}

好了介绍就到这里了,如果觉得行的话,
进入github的传送门点个star吧,谢谢!!!

关于我:

email:a1002326270@163.com

csdn:仿360手机助手下载按钮

github:enter

更多你喜欢的文章

仿360手机助手下载按钮
仿苹果版小黄车(ofo)app主页菜单效果
设计一个银行app的最大额度控件
带你实现ViewGroup规定行数、item居中的流式布局
定制一个类似地址选择器的view
3D版翻页公告效果
一分钟搞定触手app主页酷炫滑动切换效果
快速利用RecyclerView的LayoutManager搭建流式布局
用贝塞尔曲线自己写的一个电量显示的控件
快速搞定一个自定义的日历
最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 203,362评论 5 477
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 85,330评论 2 381
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 150,247评论 0 337
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 54,560评论 1 273
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 63,580评论 5 365
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 48,569评论 1 281
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 37,929评论 3 395
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 36,587评论 0 258
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 40,840评论 1 297
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 35,596评论 2 321
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 37,678评论 1 329
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 33,366评论 4 318
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 38,945评论 3 307
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 29,929评论 0 19
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 31,165评论 1 259
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 43,271评论 2 349
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 42,403评论 2 342

推荐阅读更多精彩内容

  • 作者简介 原创微信公众号郭霖 WeChat ID: guolin_blog 本篇来自的一幕的投稿,分享了一个仿照3...
    木木00阅读 802评论 1 13
  • Android 自定义View的各种姿势1 Activity的显示之ViewRootImpl详解 Activity...
    passiontim阅读 171,392评论 25 707
  • 有些人天生就是高贵美丽的,姣好的面容,显赫的家世,拥有了一切美好的东西,占据了世界的所有幸福,一个...
    初昕阅读 474评论 1 2
  • 我,有一个奇葩的表妹,姑且叫她小W。在她刚出生的时候,长得眉清目秀,眼睛亮亮的,饱含全家人对她的喜爱和宠溺。作为舅...
    如若之泠阅读 539评论 0 0
  • 思路: 首先要画出游戏背景墙; 其次,要有方块,以及方块单元; 方块的不同形状,颜色随机产生; 游戏的控制面板。 ...
    傲子阅读 2,646评论 0 3