画个知乎的弧~仿知乎日报开屏页

先看看知乎日报开屏页的效果,非常漂亮的开屏效果

ezgif.com-resize (2).gif

然后我来一个

ezgif.com-resize (1).gif

也不错感觉可以以假乱真了

很简单,直接开始。
实现这个效果先制定个三步走策略

  1. 底部布局上滑展示。
  2. 画一个知弧。
  3. 显示图片
底部布局上滑展示

直接上代码吧,属性动画基本使用

private void startAnimation() {
        //位移动画,从底部滑出,Y方向移动,mHeight是底部布局的高度
        ObjectAnimator translationAnimator= ObjectAnimator.ofFloat(rv_bottom, "translationY", mHeight, 0f);
        //设置时长
        translationAnimator.setDuration(1000);
        //透明度渐变动画
        ObjectAnimator alphaAnimatorator = ObjectAnimator.ofFloat(rv_bottom, "alpha", 0f,1f);
        //设置时长
        alphaAnimatorator.setDuration(2500);
        //添加监听器,位移结束后,画圆弧开始
        translationAnimator.addListener(new Animator.AnimatorListener() {
            @Override
            public void onAnimationStart(Animator animation) {

            }

            @Override
            public void onAnimationEnd(Animator animation) {
                zhview.startAnimation();
            }

            @Override
            public void onAnimationCancel(Animator animation) {

            }

            @Override
            public void onAnimationRepeat(Animator animation) {

            }
        });
        AnimatorSet set = new AnimatorSet();
        //两个动画一起执行
        set.play(translationAnimator).with(alphaAnimatorator);
        //go
        set.start();
    }

在位移动画结束的时候,调用了自定义的view的方法,开始了画弧的操作。

画个知弧

接下来开始画画~ 自定义一个view,重写ondraw方法,开画之前先初始化一个合适的画笔。

 private void initPaint() {
        mPaint1 = new Paint();
       //设置画笔颜色
        mPaint1.setColor(Color.WHITE);
        // 设置画笔的样式为圆形
        mPaint1.setStrokeCap(Paint.Cap.ROUND);
        // 设置画笔的填充样式为描边
        mPaint1.setStyle(Paint.Style.STROKE);
        //抗锯齿
        mPaint1.setAntiAlias(true);
        //设置画笔宽度
        mPaint1.setStrokeWidth(mBorderWidth1);

        mPaint2 = new Paint();
        mPaint2.setColor(Color.WHITE);
        mPaint2.setStyle(Paint.Style.STROKE);
        mPaint2.setAntiAlias(true);
        mPaint2.setStrokeWidth(mBorderWidth2);
    }

mPaint1用来画弧,设置填充样式为描边,这样起码我们就能轻松画一个圆环了。其实要画的知弧就是一个圆环被啃去了一块的感觉~ 但被啃的地方很光滑,所以需要一个圆头的画笔 。
mPaint2用来画外面的圆角矩形环,设置也差不多。

@Override
    protected void onDraw(Canvas canvas) {
        super.onDraw(canvas);
        canvas.drawColor(Color.BLACK);
        //矩形轮廓,圆弧在内部,给予一定的内边距
        RectF rectF1 = new RectF(mBorderWidth1/2+dipToPx(8), mBorderWidth1/2+dipToPx(8), getWidth() -mBorderWidth1/2-dipToPx(8),getWidth()-mBorderWidth1/2-dipToPx(8) );
        //画圆弧 参数1:矩形轮廓 参数2:起始位置 参数3:扫过的范围,初始为0 参数4:是否连接圆心
        canvas.drawArc(rectF1, 90, mCurrentRadian, false, mPaint1);
        //矩形轮廓
        RectF rectF2 = new RectF(mBorderWidth2/2,mBorderWidth2/2,getWidth()-mBorderWidth2/2,getWidth()-mBorderWidth2/2);
        //画圆角矩形边框 参数2 3设置x,y方向的圆角corner 都要设置
        canvas.drawRoundRect(rectF2,dipToPx(8),dipToPx(8),mPaint2);

    }

代码量很少,但要明确环的画法,不论是画圆环还是圆角矩形环,需要先确定一个基准矩形。基准矩形的位置和大小确定环的位置和大小。画弧的方法canvas.drawArc中的参数2 3设置了开始画弧的位置和画弧的范围。看一下运行效果,圆弧的起始画点在圆心的正下方,X轴正方向为0度,所以起始画点为90度。

接下来就使用不断增大画弧的范围的方式来完成动画的实现。上代码

private void startAnimationDraw() {
        //圆弧扫过范围为270度
        ValueAnimator valueAnimator=new ValueAnimator().ofFloat(0,270);
        //动画持续时间
        valueAnimator.setDuration(mDuration);
        //设置插值器,中间快两头慢
        valueAnimator.setInterpolator(new AccelerateDecelerateInterpolator());
        //添加状态监听器
        valueAnimator.addUpdateListener(new ValueAnimator.AnimatorUpdateListener() {
            @Override
            public void onAnimationUpdate(ValueAnimator animation) {
                //不断增大圆弧扫过的范围,并重绘来实现动画效果
                mCurrentRadian= (float) animation.getAnimatedValue();
                invalidate();
            }
        });
        valueAnimator.start();
    }

使用ValueAnimator.ofFloat创建一个值为0-270的动画,添加状态监听,在动画执行的过程中不断增大扫过的范围并重绘视图从而实现了画弧的动画效果。

整个过程就是canvas配合属性动画的方式完成了动态绘图,一点也不复杂。

显示图片

这里我使用的是Glide加载的本地图片,设置了延迟加载把握图片加载时机,获得更好的开屏效果

 //延时加载图片
                new Handler().postDelayed(new Runnable() {
                    @Override
                    public void run() {
                        Glide.with(MainActivity.this).
                                load(R.drawable.timg).
                                centerCrop().
                                skipMemoryCache(true).
                                diskCacheStrategy(DiskCacheStrategy.NONE).
                                crossFade(500).
                                into(image)
                        ;
                    }
                },2000);

这里个人认为知乎也是用某种方式预先把图片下载到本地完成来把握精确地加载时机,不知道是不是这样。。

最后贴一下代码

activity

public class MainActivity extends AppCompatActivity {
    private RelativeLayout rv_bottom;
    private Zhview zhview;
    private float mHeight;
    private ImageView image;

    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_main);
        rv_bottom= (RelativeLayout) this.findViewById(R.id.rv_bottom);
        zhview= (Zhview) this.findViewById(R.id.zhview);
        image= (ImageView) this.findViewById(R.id.image);
        rv_bottom.post(new Runnable() {
            @Override
            public void run() {
                //获得底部的高度
                mHeight=rv_bottom.getHeight();
                //开始动画
                startAnimation();
                //延时加载图片
                new Handler().postDelayed(new Runnable() {
                    @Override
                    public void run() {
                        Glide.with(MainActivity.this).
                                load(R.drawable.timg).
                                centerCrop().
                                skipMemoryCache(true).
                                diskCacheStrategy(DiskCacheStrategy.NONE).
                                crossFade(500).
                                into(image)
                        ;
                    }
                },2000);
            }
        });
    }

    private void startAnimation() {
        //位移动画,从底部滑出,Y方向移动
        ObjectAnimator translationAnimator= ObjectAnimator.ofFloat(rv_bottom, "translationY", mHeight, 0f);
        //设置时长
        translationAnimator.setDuration(1000);
        //透明度渐变动画
        ObjectAnimator alphaAnimatorator = ObjectAnimator.ofFloat(rv_bottom, "alpha", 0f,1f);
        //设置时长
        alphaAnimatorator.setDuration(2500);
        //添加监听器,位移结束后,画圆弧开始
        translationAnimator.addListener(new Animator.AnimatorListener() {
            @Override
            public void onAnimationStart(Animator animation) {

            }

            @Override
            public void onAnimationEnd(Animator animation) {
                zhview.startAnimation();
            }

            @Override
            public void onAnimationCancel(Animator animation) {

            }

            @Override
            public void onAnimationRepeat(Animator animation) {

            }
        });
        AnimatorSet set = new AnimatorSet();
        //两个动画一起执行
        set.play(translationAnimator).with(alphaAnimatorator);
        //go
        set.start();
    }
}

自定义view

public class Zhview extends View {
    private Paint mPaint1;  //圆弧画笔
    private Paint mPaint2;  //外框画笔
    //圆弧宽度
    private int mBorderWidth1=dipToPx(5);
    //外框宽度
    private int mBorderWidth2=dipToPx(1.5f);
    //扫过的范围
    private float mCurrentRadian=0;
    //动画持续时长
    private int mDuration=1500;

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

    public Zhview(Context context, @Nullable AttributeSet attrs) {
        this(context, attrs,0);

    }

    public Zhview(Context context, @Nullable AttributeSet attrs, int defStyleAttr) {
        super(context, attrs, defStyleAttr);
        //初始化画笔
        initPaint();
    }

    private void initPaint() {
        mPaint1 = new Paint();
       //设置画笔颜色
        mPaint1.setColor(Color.WHITE);
        // 设置画笔的样式为圆形
        mPaint1.setStrokeCap(Paint.Cap.ROUND);
        // 设置画笔的填充样式为描边
        mPaint1.setStyle(Paint.Style.STROKE);
        //抗锯齿
        mPaint1.setAntiAlias(true);
        //设置画笔宽度
        mPaint1.setStrokeWidth(mBorderWidth1);

        mPaint2 = new Paint();
        mPaint2.setColor(Color.WHITE);
        mPaint2.setStyle(Paint.Style.STROKE);
        mPaint2.setAntiAlias(true);
        mPaint2.setStrokeWidth(mBorderWidth2);
    }

    @Override
    protected void onDraw(Canvas canvas) {
        super.onDraw(canvas);
        canvas.drawColor(Color.BLACK);
        //矩形轮廓,圆弧在内部,给予一定的内边距
        RectF rectF1 = new RectF(mBorderWidth1/2+dipToPx(8), mBorderWidth1/2+dipToPx(8), getWidth() -mBorderWidth1/2-dipToPx(8),getWidth()-mBorderWidth1/2-dipToPx(8) );
        //画圆弧 参数1:矩形轮廓 参数2:起始位置 参数3:扫过的范围,初始为0 参数4:是否连接圆心
        canvas.drawArc(rectF1, 90, mCurrentRadian, false, mPaint1);
        //矩形轮廓
        RectF rectF2 = new RectF(mBorderWidth2/2,mBorderWidth2/2,getWidth()-mBorderWidth2/2,getWidth()-mBorderWidth2/2);
        //画圆角矩形边框 参数2 3设置x,y方向的圆角corner 都要设置
        canvas.drawRoundRect(rectF2,dipToPx(8),dipToPx(8),mPaint2);

    }

    private void startAnimationDraw() {
        //圆弧扫过范围为270度
        ValueAnimator valueAnimator=new ValueAnimator().ofFloat(0,270);
        //动画持续时间
        valueAnimator.setDuration(mDuration);
        //设置插值器,中间快两头慢
        valueAnimator.setInterpolator(new AccelerateDecelerateInterpolator());
        //添加状态监听器
        valueAnimator.addUpdateListener(new ValueAnimator.AnimatorUpdateListener() {
            @Override
            public void onAnimationUpdate(ValueAnimator animation) {
                //不断增大圆弧扫过的范围,并重绘来实现动画效果
                mCurrentRadian= (float) animation.getAnimatedValue();
                invalidate();
            }
        });
        valueAnimator.start();
    }
    //开始动画
    public void startAnimation(){
        startAnimationDraw();
    }
    private int dipToPx(float dip) {
        float density = getContext().getResources().getDisplayMetrics().density;
        return (int) (dip * density + 0.5f * (dip >= 0 ? 1 : -1));
    }
}

布局文件

<?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"
    android:background="@android:color/black"
    tools:context="com.zhview.MainActivity">

    <ImageView
        android:id="@+id/image"
        android:layout_width="match_parent"
        android:layout_height="match_parent"
        android:layout_above="@+id/rv_bottom" />

    <RelativeLayout
        android:id="@+id/rv_bottom"
        android:layout_width="match_parent"
        android:layout_height="wrap_content"
        android:layout_alignParentBottom="true"
        android:padding="20dp">

        <com.zhview.Zhview
            android:id="@+id/zhview"
            android:layout_width="46dp"
            android:layout_height="46dp"
            android:layout_marginLeft="10dp" />

        <TextView
            android:id="@+id/tv_title"
            android:layout_width="wrap_content"
            android:layout_height="wrap_content"
            android:layout_marginLeft="20dp"
            android:layout_toRightOf="@+id/zhview"
            android:text="知乎日报"
            android:textColor="@android:color/white"
            android:textSize="19sp"

            />

        <TextView
            android:layout_width="wrap_content"
            android:layout_height="wrap_content"
            android:layout_alignBottom="@+id/zhview"
            android:layout_marginLeft="20dp"
            android:layout_toRightOf="@+id/zhview"
            android:text="每天三次,每次七分钟"
            android:textColor="@android:color/darker_gray"
            android:textSize="13sp" />
    </RelativeLayout>
</RelativeLayout>

我个人挺喜欢这些实现起来不复杂但体验非常好的设计,见到了就努力实现一下,然后边学边分享,要是跟我一样感兴趣的话可以关注我一下哦~
完整代码地址https://github.com/yanyiqun001/zhview

最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 159,015评论 4 362
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 67,262评论 1 292
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 108,727评论 0 243
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 43,986评论 0 205
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 52,363评论 3 287
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 40,610评论 1 219
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 31,871评论 2 312
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 30,582评论 0 198
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 34,297评论 1 242
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 30,551评论 2 246
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 32,053评论 1 260
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 28,385评论 2 253
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 33,035评论 3 236
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 26,079评论 0 8
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 26,841评论 0 195
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 35,648评论 2 274
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 35,550评论 2 270

推荐阅读更多精彩内容

  • Android 自定义View的各种姿势1 Activity的显示之ViewRootImpl详解 Activity...
    passiontim阅读 170,569评论 25 707
  • 发现 关注 消息 iOS 第三方库、插件、知名博客总结 作者大灰狼的小绵羊哥哥关注 2017.06.26 09:4...
    肇东周阅读 11,613评论 4 59
  • 一 他还记得第一次看到她时的那种悸动,坐在她45度后方,无聊中向前一瞥,本已游开的目光顿时被某种力量拉了回来。中长...
    whaowhao阅读 183评论 0 2
  • 1.安装并破解Chales下载应用:点击下载charles 3.11.5破解版安装应用:以上安装文件是mac版,w...
    Jerry_unused阅读 1,118评论 0 0
  • 荇菜摇摇怜枯塘 葡萄滴水对轩窗 发际微凉知秋长 迎风清侧补秋霜 蓑衣扫竹挂瓦房 鞋履和泥裹满香 夕阳晚照乌衣巷 桂...
    梅凉阅读 487评论 0 4