先看看知乎日?qǐng)?bào)開屏頁的效果,非常漂亮的開屏效果
然后我來一個(gè)
也不錯(cuò)感覺可以以假亂真了
很簡單,直接開始宪睹。
實(shí)現(xiàn)這個(gè)效果先制定個(gè)三步走策略
- 底部布局上滑展示扫腺。
- 畫一個(gè)知弧岗照。
- 顯示圖片
底部布局上滑展示
直接上代碼吧,屬性動(dòng)畫基本使用
private void startAnimation() {
//位移動(dòng)畫笆环,從底部滑出攒至,Y方向移動(dòng),mHeight是底部布局的高度
ObjectAnimator translationAnimator= ObjectAnimator.ofFloat(rv_bottom, "translationY", mHeight, 0f);
//設(shè)置時(shí)長
translationAnimator.setDuration(1000);
//透明度漸變動(dòng)畫
ObjectAnimator alphaAnimatorator = ObjectAnimator.ofFloat(rv_bottom, "alpha", 0f,1f);
//設(shè)置時(shí)長
alphaAnimatorator.setDuration(2500);
//添加監(jiān)聽器,位移結(jié)束后躁劣,畫圓弧開始
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();
//兩個(gè)動(dòng)畫一起執(zhí)行
set.play(translationAnimator).with(alphaAnimatorator);
//go
set.start();
}
在位移動(dòng)畫結(jié)束的時(shí)候迫吐,調(diào)用了自定義的view的方法,開始了畫弧的操作账忘。
畫個(gè)知弧
接下來開始畫畫~ 自定義一個(gè)view志膀,重寫ondraw方法熙宇,開畫之前先初始化一個(gè)合適的畫筆。
private void initPaint() {
mPaint1 = new Paint();
//設(shè)置畫筆顏色
mPaint1.setColor(Color.WHITE);
// 設(shè)置畫筆的樣式為圓形
mPaint1.setStrokeCap(Paint.Cap.ROUND);
// 設(shè)置畫筆的填充樣式為描邊
mPaint1.setStyle(Paint.Style.STROKE);
//抗鋸齒
mPaint1.setAntiAlias(true);
//設(shè)置畫筆寬度
mPaint1.setStrokeWidth(mBorderWidth1);
mPaint2 = new Paint();
mPaint2.setColor(Color.WHITE);
mPaint2.setStyle(Paint.Style.STROKE);
mPaint2.setAntiAlias(true);
mPaint2.setStrokeWidth(mBorderWidth2);
}
mPaint1用來畫弧梧却,設(shè)置填充樣式為描邊奇颠,這樣起碼我們就能輕松畫一個(gè)圓環(huán)了。其實(shí)要畫的知弧就是一個(gè)圓環(huán)被啃去了一塊的感覺~ 但被啃的地方很光滑放航,所以需要一個(gè)圓頭的畫筆 烈拒。
mPaint2用來畫外面的圓角矩形環(huán),設(shè)置也差不多广鳍。
@Override
protected void onDraw(Canvas canvas) {
super.onDraw(canvas);
canvas.drawColor(Color.BLACK);
//矩形輪廓荆几,圓弧在內(nèi)部,給予一定的內(nèi)邊距
RectF rectF1 = new RectF(mBorderWidth1/2+dipToPx(8), mBorderWidth1/2+dipToPx(8), getWidth() -mBorderWidth1/2-dipToPx(8),getWidth()-mBorderWidth1/2-dipToPx(8) );
//畫圓弧 參數(shù)1:矩形輪廓 參數(shù)2:起始位置 參數(shù)3:掃過的范圍赊时,初始為0 參數(shù)4:是否連接圓心
canvas.drawArc(rectF1, 90, mCurrentRadian, false, mPaint1);
//矩形輪廓
RectF rectF2 = new RectF(mBorderWidth2/2,mBorderWidth2/2,getWidth()-mBorderWidth2/2,getWidth()-mBorderWidth2/2);
//畫圓角矩形邊框 參數(shù)2 3設(shè)置x,y方向的圓角corner 都要設(shè)置
canvas.drawRoundRect(rectF2,dipToPx(8),dipToPx(8),mPaint2);
}
代碼量很少吨铸,但要明確環(huán)的畫法,不論是畫圓環(huán)還是圓角矩形環(huán)祖秒,需要先確定一個(gè)基準(zhǔn)矩形诞吱。基準(zhǔn)矩形的位置和大小確定環(huán)的位置和大小竭缝。畫弧的方法canvas.drawArc中的參數(shù)2 3設(shè)置了開始畫弧的位置和畫弧的范圍房维。看一下運(yùn)行效果抬纸,圓弧的起始畫點(diǎn)在圓心的正下方咙俩,X軸正方向?yàn)?度,所以起始畫點(diǎn)為90度湿故。
接下來就使用不斷增大畫弧的范圍的方式來完成動(dòng)畫的實(shí)現(xiàn)阿趁。上代碼
private void startAnimationDraw() {
//圓弧掃過范圍為270度
ValueAnimator valueAnimator=new ValueAnimator().ofFloat(0,270);
//動(dòng)畫持續(xù)時(shí)間
valueAnimator.setDuration(mDuration);
//設(shè)置插值器,中間快兩頭慢
valueAnimator.setInterpolator(new AccelerateDecelerateInterpolator());
//添加狀態(tài)監(jiān)聽器
valueAnimator.addUpdateListener(new ValueAnimator.AnimatorUpdateListener() {
@Override
public void onAnimationUpdate(ValueAnimator animation) {
//不斷增大圓弧掃過的范圍坛猪,并重繪來實(shí)現(xiàn)動(dòng)畫效果
mCurrentRadian= (float) animation.getAnimatedValue();
invalidate();
}
});
valueAnimator.start();
}
使用ValueAnimator.ofFloat創(chuàng)建一個(gè)值為0-270的動(dòng)畫脖阵,添加狀態(tài)監(jiān)聽,在動(dòng)畫執(zhí)行的過程中不斷增大掃過的范圍并重繪視圖從而實(shí)現(xiàn)了畫弧的動(dòng)畫效果墅茉。
整個(gè)過程就是canvas配合屬性動(dòng)畫的方式完成了動(dòng)態(tài)繪圖独撇,一點(diǎn)也不復(fù)雜。
顯示圖片
這里我使用的是Glide加載的本地圖片躁锁,設(shè)置了延遲加載把握?qǐng)D片加載時(shí)機(jī),獲得更好的開屏效果
//延時(shí)加載圖片
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);
這里個(gè)人認(rèn)為知乎也是用某種方式預(yù)先把圖片下載到本地完成來把握精確地加載時(shí)機(jī)卵史,不知道是不是這樣战转。。
最后貼一下代碼
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();
//開始動(dòng)畫
startAnimation();
//延時(shí)加載圖片
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() {
//位移動(dòng)畫以躯,從底部滑出槐秧,Y方向移動(dòng)
ObjectAnimator translationAnimator= ObjectAnimator.ofFloat(rv_bottom, "translationY", mHeight, 0f);
//設(shè)置時(shí)長
translationAnimator.setDuration(1000);
//透明度漸變動(dòng)畫
ObjectAnimator alphaAnimatorator = ObjectAnimator.ofFloat(rv_bottom, "alpha", 0f,1f);
//設(shè)置時(shí)長
alphaAnimatorator.setDuration(2500);
//添加監(jiān)聽器啄踊,位移結(jié)束后,畫圓弧開始
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();
//兩個(gè)動(dòng)畫一起執(zhí)行
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);
//外框?qū)挾? private int mBorderWidth2=dipToPx(1.5f);
//掃過的范圍
private float mCurrentRadian=0;
//動(dòng)畫持續(xù)時(shí)長
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();
//設(shè)置畫筆顏色
mPaint1.setColor(Color.WHITE);
// 設(shè)置畫筆的樣式為圓形
mPaint1.setStrokeCap(Paint.Cap.ROUND);
// 設(shè)置畫筆的填充樣式為描邊
mPaint1.setStyle(Paint.Style.STROKE);
//抗鋸齒
mPaint1.setAntiAlias(true);
//設(shè)置畫筆寬度
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);
//矩形輪廓刁标,圓弧在內(nèi)部颠通,給予一定的內(nèi)邊距
RectF rectF1 = new RectF(mBorderWidth1/2+dipToPx(8), mBorderWidth1/2+dipToPx(8), getWidth() -mBorderWidth1/2-dipToPx(8),getWidth()-mBorderWidth1/2-dipToPx(8) );
//畫圓弧 參數(shù)1:矩形輪廓 參數(shù)2:起始位置 參數(shù)3:掃過的范圍,初始為0 參數(shù)4:是否連接圓心
canvas.drawArc(rectF1, 90, mCurrentRadian, false, mPaint1);
//矩形輪廓
RectF rectF2 = new RectF(mBorderWidth2/2,mBorderWidth2/2,getWidth()-mBorderWidth2/2,getWidth()-mBorderWidth2/2);
//畫圓角矩形邊框 參數(shù)2 3設(shè)置x,y方向的圓角corner 都要設(shè)置
canvas.drawRoundRect(rectF2,dipToPx(8),dipToPx(8),mPaint2);
}
private void startAnimationDraw() {
//圓弧掃過范圍為270度
ValueAnimator valueAnimator=new ValueAnimator().ofFloat(0,270);
//動(dòng)畫持續(xù)時(shí)間
valueAnimator.setDuration(mDuration);
//設(shè)置插值器膀懈,中間快兩頭慢
valueAnimator.setInterpolator(new AccelerateDecelerateInterpolator());
//添加狀態(tài)監(jiān)聽器
valueAnimator.addUpdateListener(new ValueAnimator.AnimatorUpdateListener() {
@Override
public void onAnimationUpdate(ValueAnimator animation) {
//不斷增大圓弧掃過的范圍顿锰,并重繪來實(shí)現(xiàn)動(dòng)畫效果
mCurrentRadian= (float) animation.getAnimatedValue();
invalidate();
}
});
valueAnimator.start();
}
//開始動(dòng)畫
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="知乎日?qǐng)?bào)"
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>
我個(gè)人挺喜歡這些實(shí)現(xiàn)起來不復(fù)雜但體驗(yàn)非常好的設(shè)計(jì)启搂,見到了就努力實(shí)現(xiàn)一下硼控,然后邊學(xué)邊分享,要是跟我一樣感興趣的話可以關(guān)注我一下哦~
完整代碼地址https://github.com/yanyiqun001/zhview