1. 程式人生 > >android 使用Path實現搜尋動態載入動畫效果

android 使用Path實現搜尋動態載入動畫效果

今天實現一個搜尋動態載入資料的動畫效果,還是先看效果吧,用文字描述乾巴巴的,看圖說話什麼都明白了,


實現這個就是使用Path中的getSegment()不斷的去改變它擷取片段的start和stop,再結合動畫,今天就分步驟實現它,看完以後你也會覺的不是很難,只是沒想到這麼實現而已,所以要多見識,所謂眼界決定你的高度,還是延續我寫部落格的習慣,一步步分析,第一步就是繪製如下圖:


如果單純的繪製這個圖很簡單很簡單的,繪製一個圓,然後再繪製一根線就搞定,但是要考慮這裡的效果,就不能這麼幹了,如果你看了上面的gif圖就知道,其實這是2個同心圓,然後前一個path的起點和後一個path的起點相連線就是形成一條直線了,但是path中的圖形內容也就是這個圓是怎麼繪製出來的呢?如果是繪製圓的話,上面的線起點和終點位置怎麼去計算,這是個問題,但是我們繪製圓還可以使用繪製橢圓的形式也是可以繪製達到圓的效果,從45度開始繪製一個圓,是不是這個線的起點搞定了,分析圖如下:


那麼好,根據上面的分析開始寫程式碼繪製出一個靜態的搜尋圖:

package com.tuya;
import android.content.Context;
import android.graphics.Canvas;
import android.graphics.Color;
import android.graphics.Paint;
import android.graphics.Path;
import android.graphics.PathMeasure;
import android.graphics.RectF;
import android.util.AttributeSet;
import android.view.View; /** * Created by admin on 2016/12/17. */ public class DynamicSearchView2 extends View { private Paint paint; private int width;//view的寬度 private int height;//view的高度 private Path searchPath; private Path circlePath; private float BigCircleRectWidth;//搜尋圓對應的外切正方形邊長 private
PathMeasure pathMeasure; private float[] pos; public DynamicSearchView2(Context context) { this(context,null); } public DynamicSearchView2(Context context, AttributeSet attrs) { this(context, attrs,0); } public DynamicSearchView2(Context context, AttributeSet attrs, int defStyleAttr) { super(context, attrs, defStyleAttr); init(); } private void init() { paint = new Paint(); paint.setAntiAlias(true); paint.setStrokeWidth(3); paint.setColor(Color.WHITE); paint.setStyle(Paint.Style.STROKE); } @Override protected void onSizeChanged(int w, int h, int oldw, int oldh) { super.onSizeChanged(w, h, oldw, oldh); width = w; height = h; initPath(); } /** * 初始化path */ private void initPath() { searchPath = new Path(); circlePath = new Path(); if(width>height){//長方形 BigCircleRectWidth = height; }else if(width<height){ BigCircleRectWidth = width; }else{ BigCircleRectWidth = width; } float smallbordWidth =BigCircleRectWidth/8; RectF searchRect = new RectF(-smallbordWidth,-smallbordWidth,smallbordWidth,smallbordWidth); searchPath.addArc(searchRect,45,360); float bigBordWidth = smallbordWidth*2; RectF circleRect = new RectF(-bigBordWidth,-bigBordWidth,bigBordWidth,bigBordWidth); circlePath.addArc(circleRect,45,-360); pathMeasure = new PathMeasure(circlePath,false); pos = new float[2]; pathMeasure.getPosTan(0,pos,null); searchPath.lineTo(pos[0],pos[1]); } @Override protected void onDraw(Canvas canvas) { super.onDraw(canvas); canvas.translate(width/2,height/2);//平移畫布把這個view的中心點當做原點 canvas.drawPath(searchPath,paint); canvas.drawPath(circlePath,paint); } }

效果圖:


本來這個外圓是不需要draw上去的,我在這繪製上去只是告訴你這二個圓是有一定的聯絡,哪為什麼這根線是這樣的呢?我們在繪製這個圓的時候是從45度開始繪製360剛好是一週,形成了一個圓,現在做個測試不要360,就寫個330度,效果如下:


這個時候你會發現這條線是對的,導致問題其實是這樣的,如圖分析:


把繪製橢圓的關鍵程式碼:

searchPath.addArc(searchRect,45,358);

circlePath.addArc(circleRect,45,-358);

不要寫成360,改為358試試,效果圖:


發現這線是不是正常了,至於外面的圓還有點缺口,第一你可以把358改成359應該沒事了,還有就是我們其實真實的效果並不需要這個外面的圓,所以不改也沒事,那麼好,第一步算是完成了,現在想想第二步怎麼實現,先把第二步的效果用gif展示看下,不然光想沒思路,就像你看美女,第一眼看那,是吧,就不多說了!要有畫面感,


還是畫布分析:


哪我們只要改變startD這個離起始點的位置值就ok,當然有很多種方法,但是android中基本上都是使用值動畫,ok,根據這個思路實現這個第二步邏輯:

package com.tuya;
import android.animation.Animator;
import android.animation.ValueAnimator;
import android.content.Context;
import android.graphics.Canvas;
import android.graphics.Color;
import android.graphics.Paint;
import android.graphics.Path;
import android.graphics.PathMeasure;
import android.graphics.RectF;
import android.util.AttributeSet;
import android.view.View;
/**
 * Created by admin on 2016/12/17.
 */
public class DynamicSearchView2 extends View {
    private Paint paint;
    private int width;//view的寬度
private int height;//view的高度
private Path searchPath;
    private Path circlePath;
    private float BigCircleRectWidth;//搜尋圓對應的外切正方形邊長
private PathMeasure pathMeasure;
    private float[] pos;
    private float animPercent;//
private ValueAnimator serchStartAnim;
    private long animDuration = 2000;//動畫時間
public DynamicSearchView2(Context context) {
        this(context,null);
}
    public DynamicSearchView2(Context context, AttributeSet attrs) {
        this(context, attrs,0);
}
    public DynamicSearchView2(Context context, AttributeSet attrs, int defStyleAttr) {
        super(context, attrs, defStyleAttr);
init();
}
    private void init() {
        initPaint();
initAnim();
initAnimListener();
startAnim();
}
    /**
     * 開始執行動畫
*/
private void startAnim() {
        serchStartAnim.start();
}
    /**
     * 動畫監聽
*/
private void initAnimListener() {
        serchStartAnim.addUpdateListener(new ValueAnimator.AnimatorUpdateListener(){
            @Override
public void onAnimationUpdate(ValueAnimator valueAnimator) {
                 //獲取動畫在單位時間內,每次執行的值
animPercent = (float) valueAnimator.getAnimatedValue();
invalidate();
}
        });
}
    /**
     * 初始化動畫
*/
private void initAnim() {
        serchStartAnim = ValueAnimator.ofFloat(0,1).setDuration(animDuration);
}
    /**
     * 初始化畫筆
*/
private void initPaint() {
        paint = new Paint();
paint.setAntiAlias(true);
paint.setStrokeWidth(3);
paint.setColor(Color.WHITE);
paint.setStyle(Paint.Style.STROKE);
}

    @Override
protected void onSizeChanged(int w, int h, int oldw, int oldh) {
        super.onSizeChanged(w, h, oldw, oldh);
width = w;
height = h;
initPath();
}
    /**
     * 初始化path
     */
private void initPath() {
        searchPath = new Path();
circlePath = new Path();
        if(width>height){//長方形
BigCircleRectWidth = height;
}else if(width<height){
            BigCircleRectWidth = width;
}else{
            BigCircleRectWidth = width;
}
        float smallbordWidth =BigCircleRectWidth/8;
RectF searchRect = new RectF(-smallbordWidth,-smallbordWidth,smallbordWidth,smallbordWidth);
searchPath.addArc(searchRect,45,358);
        float bigBordWidth = smallbordWidth*2;
RectF circleRect = new RectF(-bigBordWidth,-bigBordWidth,bigBordWidth,bigBordWidth);
circlePath.addArc(circleRect,45,-358);
pathMeasure = new PathMeasure(circlePath,false);
pos = new float[2];
pathMeasure.getPosTan(0,pos,null);
searchPath.lineTo(pos[0],pos[1]);
}

    @Override
protected void onDraw(Canvas canvas) {
        super.onDraw(canvas);
canvas.translate(width/2,height/2);//平移畫布把這個view的中心點當做原點
drawSearch(canvas);
}
    private void drawSearch(Canvas canvas) {
        Path dst = new Path();
pathMeasure.setPath(searchPath,false);
pathMeasure.getSegment(pathMeasure.getLength()*animPercent,pathMeasure.getLength(),dst,true);
canvas.drawPath(searchPath,paint);
}
}
效果:


現在還我們效果還差外圓的大圓的效果了,那麼大圓是在小圓動畫執行完畢後再去做旋轉效果的,那好,我們只要監聽動畫就可以,畫圖:

package com.tuya;
import android.animation.Animator;
import android.animation.ValueAnimator;
import android.content.Context;
import android.graphics.Canvas;
import android.graphics.Color;
import android.graphics.Paint;
import android.graphics.Path;
import android.graphics.PathMeasure;
import android.graphics.RectF;
import android.util.AttributeSet;
import android.view.View;
/**
 * Created by admin on 2016/12/17.
 */
public class DynamicSearchView2 extends View {
    private static final String TAG = "DynamicSearchView2";
    private Paint paint;
    private int width;//view的寬度
private int height;//view的高度
private Path searchPath;
    private Path circlePath;
    private float BigCircleRectWidth;//搜尋圓對應的外切正方形邊長
private PathMeasure pathMeasure;
    private float[] pos;
    private float animPercent;//
private ValueAnimator serchStartAnim;
    private ValueAnimator bigCircleAnim;//外面大圓運動的動畫
private long animDuration = 2000;//動畫時間
private int drawTag = 1;//區分是繪製搜尋框還是外層圓
public DynamicSearchView2(Context context) {
        this(context,null);
}
    public DynamicSearchView2(Context context, AttributeSet attrs) {
        this(context, attrs,0);
}
    public DynamicSearchView2(Context context, AttributeSet attrs, int defStyleAttr) {
        super(context, attrs, defStyleAttr);
init();
}
    private void init() {
        initPaint();
initAnim();
initAnimListener();
startAnim();
}
    /**
     * 開始執行動畫
*/
private void startAnim() {
        drawTag = 1;
serchStartAnim.start();
invalidate();
}
    /**
     * 開啟大圓執行動畫
*/
public void startBigCirCleAnim(){
        serchStartAnim.removeAllUpdateListeners();//把上一個動畫監聽移除 以免總成詭異的bug
bigCircleAnim.start();
drawTag = 2;
}
    /**
     * 動畫監聽
*/
private void initAnimListener() {
        serchStartAnim.addListener(new Animator.AnimatorListener() {
            @Override
public void onAnimationStart(Animator animator) {

            }

            @Override
public void onAnimationEnd(Animator animator) {
                startBigCirCleAnim();
}

            @Override
public void onAnimationCancel(Animator animator) {

            }

            @Override
public void onAnimationRepeat(Animator animator) {

            }
        });
serchStartAnim.addUpdateListener(new ValueAnimator.AnimatorUpdateListener(){
            @Override
public void onAnimationUpdate(ValueAnimator valueAnimator) {
                //獲取動畫在單位時間內,每次執行的值
animPercent = (float) valueAnimator.getAnimatedValue();
invalidate();
}
        });
bigCircleAnim.addUpdateListener(new ValueAnimator.AnimatorUpdateListener(){
            @Override
public void onAnimationUpdate(ValueAnimator valueAnimator) {
                //獲取動畫在單位時間內,每次執行的值
animPercent = (float) valueAnimator.getAnimatedValue();
invalidate();
}
        });
}
    /**
     * 初始化動畫
*/
private void initAnim() {
        bigCircleAnim = ValueAnimator.ofFloat(0,1).setDuration(animDuration);
serchStartAnim = ValueAnimator.ofFloat(0,1).setDuration(animDuration);
}
    /**
     * 初始化畫筆
*/
private void initPaint() {
        paint = new Paint();
paint.setAntiAlias(true);
paint.setStrokeWidth(3);
paint.setColor(Color.WHITE);
paint.setStyle(Paint.Style.STROKE);
}

    @Override
protected void onSizeChanged(int w, int h, int oldw, int oldh) {
        super.onSizeChanged(w, h, oldw, oldh);
width = w;
height = h;
initPath();
}
    /**
     * 初始化path
     */
private void initPath() {
        searchPath = new Path();
circlePath = new Path();
        if(width>height){//長方形
BigCircleRectWidth = height;
}else if(width<height){
            BigCircleRectWidth = width;
}else{
            BigCircleRectWidth = width;
}
        float smallbordWidth =BigCircleRectWidth/8;
RectF searchRect = new RectF(-smallbordWidth,-smallbordWidth,smallbordWidth,smallbordWidth);
searchPath.addArc(searchRect,45,358);
        float bigBordWidth = smallbordWidth*2;
RectF circleRect = new RectF(-bigBordWidth,-bigBordWidth,bigBordWidth,bigBordWidth);
circlePath.addArc(circleRect,45,-358);
pathMeasure = new PathMeasure(circlePath,false);
pos = new float[2];
pathMeasure.getPosTan(0,pos,null);
searchPath.lineTo(pos[0],pos[1]);
}

    @Override
protected void onDraw(Canvas canvas) {
        super.onDraw(canvas);
canvas.translate(width/2,height/2);//平移畫布把這個view的中心點當做原點
drawSearch(canvas);
}
    private void drawSearch(Canvas canvas) {
        if(drawTag==1){
            drawSearchGraph(canvas);
}else if(drawTag==2){
            drawBigCircleGraph(canvas);
}
    }
    /**
     * 繪製外層大圓
* @param canvas
*/
private void drawBigCircleGraph(Canvas canvas) {
        pathMeasure.setPath(circlePath, false);
Path dst2 = new Path();
        float stop = pathMeasure.getLength() * animPercent;
        float start = (float) (stop - ((0.5 - Math.abs(animPercent - 0.5)) * 200f));
pathMeasure.getSegment(start, stop, dst2, true);
canvas.drawPath(dst2, paint);
}
    /**
     * 繪製搜尋框
* @param canvas
*/
private void drawSearchGraph(Canvas canvas) {
        pathMeasure.setPath(searchPath,false);
Path dst = new Path();
pathMeasure.getSegment(pathMeasure.getLength()*animPercent,pathMeasure.getLength(),dst,true);
canvas.drawPath(dst,paint);
}
}
效果:


發現轉一圈就到頭了,如果有特定的需求肯定是要控制整個轉圈的圈數,如果是網路載入的話,除非網路特別的好,先不管了,因為等下還要寫週報,也是很痛苦的

現在還差最後一步就是大圓的運動完後要繪製搜尋框出來,其實這個和第一步效果剛好是相關的,

package com.tuya;
import android.animation.Animator;
import android.animation.ValueAnimator;
import android.content.Context;
import android.graphics.Canvas;
import android.graphics.Color;
import android.graphics.Paint;
import android.graphics.Path;
import android.graphics.PathMeasure;
import android.graphics.RectF;
import android.util.AttributeSet;
import android.view.View;
/**
 * Created by admin on 2016/12/17.
 */
public class DynamicSearchView2 extends View {
    private static final String TAG = "DynamicSearchView2";
    private Paint paint;
    private int width;//view的寬度
private int height;//view的高度
private Path searchPath;
    private Path circlePath;
    private float BigCircleRectWidth;//搜尋圓對應的外切正方形邊長
private PathMeasure pathMeasure;
    private float[] pos;
    private float animPercent;//
private ValueAnimator serchStartAnim;
    private ValueAnimator bigCircleAnim;//外面大圓運動的動畫
private ValueAnimator startDrawSearchAnim;//最後一步繪製搜尋框
private long animDuration = 2000;//動畫時間
private int drawTag = 1;//區分是繪製搜尋框還是外層圓
public DynamicSearchView2(Context context) {
        this(context,null);
}
    public DynamicSearchView2(Context context, AttributeSet attrs) {
        this(context, attrs,0);
}
    public DynamicSearchView2(Context context, AttributeSet attrs, int defStyleAttr) {
        super(context, attrs, defStyleAttr);
init();
}
    private void init() {
        initPaint();
initAnim();
initAnimListener();
startAnim();
}
    /**
     * 開始執行動畫
*/
private void startAnim() {
        drawTag = 1;
serchStartAnim.start();
invalidate();
}
    /**
     * 開啟大圓執行動畫
*/
public void startBigCirCleAnim(){
        serchStartAnim.removeAllUpdateListeners();//把上一個動畫監聽移除 以免總成詭異的bug
bigCircleAnim.start();
drawTag = 2;
}
    /**
     * 最後繪製搜尋框的動畫
*/
public void drawSearchAanim(){
        bigCircleAnim.removeAllUpdateListeners();//把上一個動畫監聽移除 以免總成詭異的bug
startDrawSearchAnim.start();
drawTag = 3;
}
    /**
     * 動畫監聽
*/
private void initAnimListener() {
        bigCircleAnim.addListener(new Animator.AnimatorListener() {
            @Override
public void onAnimationStart(Animator animator) {

            }

            @Override
public void onAnimationEnd(Animator animator) {
                drawSearchAanim();
}
            @Override
public void onAnimationCancel(Animator animator) {

            }

            @Override
public void onAnimationRepeat(Animator animator) {

            }
        });
serchStartAnim.addUpdateListener(new ValueAnimator.AnimatorUpdateListener(){
            @Override
public void onAnimationUpdate(ValueAnimator valueAnimator) {
                //獲取動畫在單位時間內,每次執行的值
animPercent = (float) valueAnimator.getAnimatedValue();
invalidate();
}
        });
serchStartAnim.addListener(new Animator.AnimatorListener() {
            @Override
public void onAnimationStart(Animator animator) {

            }

            @Override
public void onAnimationEnd(Animator animator) {
                startBigCirCleAnim();
}

            @Override
public void onAnimationCancel(Animator animator) {

            }

            @Override
public void onAnimationRepeat(Animator animator) {

            }
        });
serchStartAnim.addUpdateListener(new ValueAnimator.AnimatorUpdateListener(){
            @Override
public void onAnimationUpdate(ValueAnimator valueAnimator) {
                
            
           

相關推薦

android 使用Path實現搜尋動態載入動畫效果

今天實現一個搜尋動態載入資料的動畫效果,還是先看效果吧,用文字描述乾巴巴的,看圖說話什麼都明白了, 實現這個就是使用Path中的getSegment()不斷的去改變它擷取片段的start和stop,再結合動畫,今天就分步驟實現它,看完以後你也會覺的不是很難,只是沒想到這麼

Android高效率實現彈出帶動畫效果的對話方塊,仿照微信對話方塊效果

看到很多app頁面裡都有彈出對話方塊效果,今天使用PopupWindow實現帶動畫效果的對話方塊,先看效果圖: 效果圖: 分析實現思路: 彈出對話方塊帶有動畫效果 對話方塊從底部向上彈出 彈出對話方塊時,窗體背景呈現灰色(半透明) 對話方塊四周具

android仿支付寶螞蟻森林載入動畫效果

一圖勝千言 偷過別人能量的小夥伴都熟悉這個載入效果,下面就講解一下實現過程。 1,自定義view 2,這裡要用到螞蟻森林的圖示,如圖 通過canvas.drawBitmap()畫出圖片。 3,通過PorterDuff.Mode.SRC_IN,給圖片填充想要的

SVG 實現動態模糊動畫效果

今天我們將向大家展示如何製作SVG動態模糊效果,並將其應用於HTML元素的常規JS或CSS動畫。 動態模糊是一種廣泛使用於動態影像和動畫的技術,它能使動作看起來更加平滑自然。 線上演示原始碼下載 動態模糊是靜止影象或一系列影象(如電影或動畫)中快速移動物體的明顯影象拖尾。當記錄的影象在單幀記錄期間發生變化

Android EditText 文字框實現搜尋和清空效果

前言   本文實現的效果:文字框輸入為空時顯示輸入的圖示;不為空時顯示清空的圖示,此時點選清空圖示能清空文字框內輸入文字。 宣告   歡迎轉載,但請保留文章原始出處:)      部落格園:http://www.cnblogs.com     農民伯伯: http://over140.cnblogs.

css3實現三種不同的loading載入動畫效果

HTML: <div class="box"> <div class="loader"> <div class="loading-1">

Android實現資源動態載入的兩種方式

這是Android Apk源載入機制原理分析以及動態載入實現系列文章 的最後一篇。經過前兩篇的介紹之後,相關基礎都講的差不多了,現在要實現自己專案中的資源載入框架,這裡提供兩種方式,區別在於由誰來載入資源。 1、利用系統載入資源Apk 2、主動手動實現資

Android自定義view-高仿小米視訊載入動畫效果

*本篇文章已授權微信公眾號 guolin_blog (郭霖)獨家釋出 1、概述         前幾日出差,每晚回到酒店的時候,睡前打發時間就是拿起自己的小米手機擼劇,酒店的wifi網路實在太差,眼睜睜的看著小米視訊的載入動畫一直拼命的loading中,正好最近一直在看

安卓自定義控制元件-實現IOS版UC瀏覽器三點載入動畫效果

1.實現分析 廢話不多說,看下IOS版UC瀏覽器的載入效果 簡單畫個圖看下整個過程 1.B圓的圓心移動的座標為:A圓和B圓的圓心的距離L的中點為圓心O1的下半圓的運動軌跡經過的座標,就有一個由B位置到A位置圓周運動的軌跡。 2.C圓的圓心

android Activity.this.getLayoutInflater()::動態載入佈局檔案,實現彈窗效

activity類的拓展方法1:getLayoutInflater():將layout的xml佈局檔案例項化為View類物件,實現動態載入佈局 MainActivity.java package com.example.dynamiclayout; import and

利用css3的animation實現點點點loading動畫效果(二)

設置 str ack rdp 提交 ssi frame spin color box-shadow實現的打點效果 簡介 box-shadow理論上可以生成任意的圖形效果,當然也就可以實現點點點的loading效果了。 實現原理 html代碼,首先需要寫如下html代

利用 :before :after偽類實現鼠標懸浮動畫效果

type 兼容 str log 參考 tex 觸發 strong nsf 1、最近在逛網站的時候,想找一下喜歡的鼠標懸浮效果,避免廣告的嫌疑,直接放圖了: 2、在實現的時候,如果在直接使用鼠標hover ,transform,進行過渡,不能達到想要的效果,因為同

關於ValueAnimation以及Interpolator +Drawable實現的自己定義動畫效果

idt edit 由於 height over 等等 -s line alt ValueAnimation : Android中的屬性動畫,他跟objectAnim

WPF實現3D翻轉的動畫效果

紋理映射 svi host child rotate diffuse lean mouse sem 1、前端代碼實現 1.1 原理見代碼註析 1 <Grid MouseDown="Grid_MouseDown"> 2 <Viewport3D&

jQuery實現加入購物車飛入動畫效果之開發不停,填坑不止(起點位置在Y軸方向位置偏移)

開發時為了完成購物車的飛入拋物線,因為懶惰隨大流使用了fly.js外掛,用的時候遇到的兩個坑坑~~ 1. 有滾動條時,拋物體的起點位置在Y軸方向上有位置偏移,偏大 2. 頁面有滾動條時,拋物體的結束位置不一樣,偏大 我:(⊙o⊙)…煩煩的。。。這就是用別人東西的代價 。。。。。。 不

Jquery實現購物車的新增動畫效果

//這塊獲取的購物車的位置 #cartN這個是購物車的id var offset = $('#cartN').offset(); //這塊是店家新增的時候的時候的操作 $(".add1").click(function () {         &

用canvas實現紅心飄飄的動畫效果

兩週前,專案裡需要實現一個紅心飄飄的點贊效果。抓耳撓腮了老半天,看了幾篇大佬的文章,終於算是摸了個七七八八。不禁長嘆一聲,還是菜啊。先來看一下效果:(傳送門進去點一波) 一、Bezier曲線運動軌跡 其實用大白話描述一下需求就是讓一個紅心圖片沿著貝塞爾曲線的軌跡走,然後邊走邊消失。核心在於得到貝塞爾曲線

藉助 Webpack 靜態分析能力實現程式碼動態載入

Debugger 一個動態配置程式碼非同步載入引發的狀態錯誤問題,想起以前在某廠學習的一個解決問題的方法論: 現象背後真實問題是啥? 真實問題背後原因是啥? 對策是要基於原因的,不是基於現象的。 最後從 Webpack 的角度利用靜態程式碼分析的能力來解決問題。 現象 父元件 kit

php+ajax實現登入按鈕載入loading效果

php+ajax實現登入按鈕載入loading效果,一個提高使用者體驗,二個避免重複提交表單,ajax判斷載入是否完成。   登入表單 1 <form onsubmit="return check_login()" style="text-align: center;margi

實現卡片翻轉的動畫效果

歡迎Follow我的GitHub, 關注我的CSDN. 在Android設計中, 經常會使用卡片元素, 正面顯示圖片或主要資訊, 背面顯示詳細內容, 如網易有道詞典的單詞翻轉和海底撈的食譜展示. 實現卡片檢視非常容易, 那麼如何實現翻轉