Lottie支持复杂动画(json)使用笔记

来源:互联网 发布:淘宝书店 编辑:程序博客网 时间:2024/06/13 10:19

Lottie支持复杂动画(json)使用笔记

基础信息

  • Lottie Git开源地址(都给出只是方便大家找,其实我本人只用到Android)
    • Android:地址
    • iOS:地址
    • React Native:地址
  • JSON文件需要Bodymovin导出的json文件
    • Bodymovin:地址
  • 官方说明:地址
  • 官方Demo(Google市场):地址

Lottie相关信息

  • 官方案例丢在Google应用市场了,国内不好下载。所以干脆自己打包Demo 百度云下载
    • 如果是要研究代码,又不想用Git,可以自己反编译看看
    • 我下载它是因为json动画文件不好弄:说说我怎么取的资源吧,尽管大多数都知道。
      1. 直接apk扩展名改成zip
      2. 使用压缩文件打开
      3. assets目录里面全部是需要的文件
      4. 我也准备了百度云链接

Lottie使用笔记

  • 设置动画文件,优先匹配代码,代码没设置,显示的才会是布局文件的配置。

初始化配置

  • Lottie要求最低编辑版本是16(Android4.1)

    minSdkVersion 16
  • Gradle注册添加支持

    dependencies {    compile 'com.airbnb.android:lottie:1.0.1'}
  • 添加json动画文件到资产目录(app/src/main/assets)
  • 给使用到该控件的布局文件根标签添加(如果你在布局文件设置的话,如果没有,请忽略)

    xmlns:app="http://schemas.android.com/apk/res-auto"

展示动画

  • 布局文件

    /** * lottie_fileName json文件名 * lottie_loop 是否循环播放 * lottie_autoPlay 是否自动播放<com.airbnb.lottie.LottieAnimationView        android:id="@+id/animation_view"        android:layout_width="wrap_content"        android:layout_height="wrap_content"        app:lottie_fileName="hello-world.json"        app:lottie_loop="true"        app:lottie_autoPlay="true" />
  • 代码实现

    LottieAnimationView animationView = (LottieAnimationView) findViewById(R.id.animation_view);// 设置json文件animationView.setAnimation("helloworld.json");// 设置是否循环播放animationView.loop(true);// 播放动画animationView.playAnimation();// 暂停动画:貌似有点不同animationView.cancelAnimation();// 停止动画:我感觉两个效果顺序是颠倒的,使用到时候请测试看看吧animationView.pauseAnimation();// 跳转进度(0.0-1.1)animationView.setProgress(float f);// 在监听中可以添加代码设置动画时长animator.setDuration(1000L);
  • 切换动画

    // 最简单的,但是需要注意,只适用于小Json文件,大的Json加载时间过长,中间可能空出来。// animationView.setAnimation("LottieLogo2.json");// animationView.playAnimation();// 官方还给出另外一种标准的切换方式LottieComposition.fromAssetFileName(act, "LottieLogo2.json",        new LottieComposition.OnCompositionLoadedListener() {            @Override            public void onCompositionLoaded(LottieComposition composition) {                animationView.setComposition(composition);                animationView.playAnimation();            }        });
  • 设置监听

    // 播放的文件更新的时候,也可以理解每一帧都调用,没想到应用场景,反正更一个动画就不停的调用。animationView.addAnimatorUpdateListener(new ValueAnimator.AnimatorUpdateListener() {    @Override    public void onAnimationUpdate(ValueAnimator valueAnimator) {    }});// 常用的监听,很多都很有用处。animationView.addAnimatorListener(new Animator.AnimatorListener() {    // 动画开始调用    @Override    public void onAnimationStart(Animator animator) {    }    // 如果设置loop为true,永远不会调用    @Override    public void onAnimationEnd(Animator animator) {    }    // 动画取消监听,监听的是Cancel方法,可是还是进度条暂停的状态。    @Override    public void onAnimationCancel(Animator animator) {    }    // 动画重复,第一次播放不是重复,不包含在内,切换动画也一样。    @Override    public void onAnimationRepeat(Animator animator) {    }});
  • 本地文件展示

    • 这个可以直接打开系统的文件管理器

      Intent intent = new Intent(Intent.ACTION_GET_CONTENT);intent.setType("*/*");intent.addCategory(Intent.CATEGORY_OPENABLE);try {    startActivityForResult(Intent.createChooser(intent, "请选择一个JSON文件"), PLAYER_BY_FILE);} catch (android.content.ActivityNotFoundException ex) {    Toast.makeText(act, "请安装一个文件管理器。", Toast.LENGTH_SHORT).show();}
    • 在这里接收选择的文件

      @Overrideprotected void onActivityResult(int requestCode, int resultCode, Intent data) {    if(requestCode == PLAYER_BY_FILE){        Uri uri = data.getData();        InputStream fis;        try {            switch (uri.getScheme()) {                case "file":                    fis = new FileInputStream(uri.getPath());                    break;                case "content":                    fis = act.getContentResolver().openInputStream(uri);                    break;                default:                    Toast.makeText(act, "加载失败!", Toast.LENGTH_SHORT).show();                    return;            }        } catch (FileNotFoundException e) {            Toast.makeText(act, "请安装一个文件管理器。", Toast.LENGTH_SHORT).show();            return;        }    }}
    • 根据返回的 输入流 InputStream 来展示Json动画

      LottieComposition        .fromInputStream(act, fis, new LottieComposition.OnCompositionLoadedListener() {            @Override            public void onCompositionLoaded(LottieComposition composition) {                animationView.setComposition(composition);                animationView.playAnimation();            }        });
  • 根据网络展示

    // str 就是联网请求到的json字符串JSONObject jsonObject = null;try {    jsonObject = new JSONObject(str);} catch (JSONException e) {    e.printStackTrace();}LottieComposition        .fromJson(getResources(), jsonObject, new LottieComposition.OnCompositionLoadedListener() {            @Override            public void onCompositionLoaded(LottieComposition composition) {                animationView.setComposition(composition);                animationView.playAnimation();            }        });
  • 引导界面动画

    这个,建议别看官方的Demo,引用第三方的工具类,反正我没用过那个类,只能一行一行的分析。最后,我发现实际上只是做了一个ViewPager的滑动监听,他之所以用那个是为了美观。如果谁有兴趣,可以使用一下试试看。

    1. 布局文件使用RelativeLayout,在 LottieAnimationView 上面添加一个ViewPager

      <?xml version="1.0" encoding="utf-8"?><RelativeLayout    xmlns:android="http://schemas.android.com/apk/res/android"    xmlns:tools="http://schemas.android.com/tools"    android:id="@+id/activity_change_pager"    android:layout_width="match_parent"    android:layout_height="match_parent"    tools:context="ll.withwings.testlottieanimation.lottie.ChangePagerActivity">    <com.airbnb.lottie.LottieAnimationView        android:id="@+id/animation_view"        android:layout_width="wrap_content"        android:layout_height="wrap_content" />    <android.support.v4.view.ViewPager        android:id="@+id/vp_show_animation"        android:layout_width="match_parent"        android:layout_height="match_parent">    </android.support.v4.view.ViewPager></RelativeLayout>
    2. 代码方面设置监听ViewPager滑动。lerp方法可以根据自己喜欢修改速度。

      // ViewPager 使用透明的Fragment填充// 设置 LottieAnimationView 动画的进度与ViewPager联动/** * 这里之所以多一个1f,是为了ViewPager最后一个item不能滑动准备的(值是根据EmptyFragment数量计算的) */private static final float[] ANIMATION_TIMES = new float[]{        0f,        0.3333f,        0.6666f,        1f,        1f};/** * 为了ViewPager联动效果准备的空Fragment。 */private List<EmptyFragment> emptyFragments;……mVpShowAnimation.addOnPageChangeListener(new ViewPager.OnPageChangeListener() {    @Override    public void onPageScrolled(int position, float positionOffset, int positionOffsetPixels) {        setAnimationProgress(position, positionOffset);    }    @Override    public void onPageSelected(int position) {    }    @Override    public void onPageScrollStateChanged(int state) {    }});private void setAnimationProgress(int position, float positionOffset) {    float startProgress = ANIMATION_TIMES[position];    float endProgress = ANIMATION_TIMES[position + 1];    // 更新动画进度    animationView.setProgress(lerp(startProgress, endProgress, positionOffset));}// 根据ViewPager拖动偏移比例来计算位置private float lerp(float startValue, float endValue, float f) {    return startValue + f * (endValue - startValue);}

字母特效动画

如果谁用到的话,我建议是用我这个代码,官方代码为了兼容性删减了很多功能。当然,如果用官方的,只需要复制官方Git里面 LottieFontViewGroup 这个文件即可

*   首先复制文件 LottieFontViewGroup.java 到自己工程*   需要记得加上监听,onDestroy 时移除监听。可以让控件根据输入内容自动滚动。        @Override        protected void initListener() {            // 这个监听可以根据换行自动滑动            fontView.getViewTreeObserver().addOnGlobalLayoutListener(layoutListener);        }        @Override        protected void onDestroy() {            // add的监听还要删除            fontView.getViewTreeObserver().removeOnGlobalLayoutListener(layoutListener);            super.onDestroy();        }        // 监听操作,建议直接复制走。        private final ViewTreeObserver.OnGlobalLayoutListener layoutListener = new ViewTreeObserver.OnGlobalLayoutListener() {            @Override            public void onGlobalLayout() {                scrollView.fullScroll(View.FOCUS_DOWN);            }        };
  • 附上我改的:

    public class LottieFontViewGroup extends FrameLayout {    private final Map<String, LottieComposition> compositionMap = new HashMap<>();    private final List<View> views = new ArrayList<>();    @Nullable    private LottieAnimationView cursorView;    public LottieFontViewGroup(Context context) {        super(context);        init();    }    public LottieFontViewGroup(Context context, AttributeSet attrs) {        super(context, attrs);        init();    }    public LottieFontViewGroup(Context context, AttributeSet attrs, int defStyleAttr) {        super(context, attrs, defStyleAttr);        init();    }    private void init() {        setFocusableInTouchMode(true);        LottieComposition.fromAssetFileName(getContext(), "Mobilo/BlinkingCursor.json",                new LottieComposition.OnCompositionLoadedListener() {                    @Override                    public void onCompositionLoaded(LottieComposition composition) {                        cursorView = new LottieAnimationView(getContext());                        cursorView.setLayoutParams(new LayoutParams(                                ViewGroup.LayoutParams.WRAP_CONTENT,                                ViewGroup.LayoutParams.WRAP_CONTENT                        ));                        cursorView.setComposition(composition);                        cursorView.loop(true);                        cursorView.playAnimation();                        addView(cursorView);                    }                });    }    /**     * 根据当前状态更新软键盘状态     */    public void changeInputType() {        InputMethodManager imm = (InputMethodManager) getContext().getSystemService(Context.INPUT_METHOD_SERVICE);        imm.toggleSoftInput(0, InputMethodManager.HIDE_NOT_ALWAYS);    }    /**     * 判断软键盘状态     *     * @return true代表打开,false代表隐藏     */    public boolean getInputType() {        InputMethodManager imm = (InputMethodManager) getContext().getSystemService(Context.INPUT_METHOD_SERVICE);        boolean isOpen = imm.isActive();//isOpen若返回true,则表示输入法打开        return isOpen;    }    /**     * 更改软键盘显示     *     * @param isOpen     */    public void setInputType(boolean isOpen) {        InputMethodManager imm = (InputMethodManager) getContext().getSystemService(Context.INPUT_METHOD_SERVICE);        if (isOpen) {            imm.showSoftInput(this, InputMethodManager.SHOW_FORCED);        } else {            // 强制隐藏键盘            imm.hideSoftInputFromWindow(this.getWindowToken(), 0);        }    }    private String string = "";    /**     * 获取当前字符串。     */    public String getString() {        return string;    }    /**     * ASCII 码转字符串     *     * @param ascii     * @return 文件字符串     */    public String asciiToString(int ascii) {        StringBuffer sbu = new StringBuffer(string);        sbu.append((char) ascii);        return new String(sbu);    }    private float downX;    private float downY;    /**     * 点击控件,切换软键盘显示     * @param event     * @return     */    @Override    public boolean onTouchEvent(MotionEvent event) {        if(event.getAction()==MotionEvent.ACTION_DOWN){            downX = event.getX();            downY = event.getY();        }else if(event.getAction() == MotionEvent.ACTION_UP && event.getX()== downX && event.getY() == downY){            changeInputType();        }        return true;    }    private void addSpace() {        int index = indexOfChild(cursorView);        addView(createSpaceView(), index);    }    @Override    public void addView(View child, int index) {        super.addView(child, index);        if (index == -1) {            views.add(child);        } else {            views.add(index, child);        }    }    private void removeLastView() {        if (views.size() > 1) {            int position = views.size() - 2;            removeView(views.get(position));            views.remove(position);        }    }    @Override    protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {        super.onMeasure(widthMeasureSpec, heightMeasureSpec);        if (views.isEmpty()) {            return;        }        int currentX = getPaddingTop();        int currentY = getPaddingLeft();        for (int i = 0; i < views.size(); i++) {            View view = views.get(i);            if (!fitsOnCurrentLine(currentX, view)) {                if (view.getTag() != null && view.getTag().equals("Space")) {                    continue;                }                currentX = getPaddingLeft();                currentY += view.getMeasuredHeight();            }            currentX += view.getWidth();        }        setMeasuredDimension(getMeasuredWidth(),                currentY + views.get(views.size() - 1).getMeasuredHeight() * 2);    }    @Override    protected void onLayout(boolean changed, int left, int top, int right, int bottom) {        if (views.isEmpty()) {            return;        }        int currentX = getPaddingTop();        int currentY = getPaddingLeft();        for (int i = 0; i < views.size(); i++) {            View view = views.get(i);            if (!fitsOnCurrentLine(currentX, view)) {                if (view.getTag() != null && view.getTag().equals("Space")) {                    continue;                }                currentX = getPaddingLeft();                currentY += view.getMeasuredHeight();            }            view.layout(currentX, currentY, currentX + view.getMeasuredWidth(),                    currentY + view.getMeasuredHeight());            currentX += view.getWidth();        }    }    @Override    public InputConnection onCreateInputConnection(EditorInfo outAttrs) {        BaseInputConnection fic = new BaseInputConnection(this, false);        outAttrs.actionLabel = null;        outAttrs.inputType = InputType.TYPE_NULL;        outAttrs.imeOptions = EditorInfo.IME_ACTION_NEXT;        return fic;    }    @Override    public boolean onCheckIsTextEditor() {        return true;    }    @Override    public boolean onKeyUp(int keyCode, KeyEvent event) {        if (keyCode == KeyEvent.KEYCODE_SPACE) {            string += " ";            addSpace();            return true;        }        if (keyCode == KeyEvent.KEYCODE_DEL) {            if(string.length()>0){                string = string.substring(0, string.length() - 1);            }else{                string = "";            }            removeLastView();            return true;        }        if (!isValidKey(event)) {            return super.onKeyUp(keyCode, event);        }        String letter = "" + Character.toUpperCase((char) event.getUnicodeChar());        // switch (letter) {        //     case ",":        //         letter = "Comma";        //         break;        //     case "'":        //         letter = "Apostrophe";        //         break;        //     case ";":        //     case ":":        //         letter = "Colon";        //         break;        // }        final String fileName = "Mobilo/" + letter + ".json";        if (compositionMap.containsKey(fileName)) {            addComposition(compositionMap.get(fileName));        } else {            LottieComposition.fromAssetFileName(getContext(), fileName,                    new LottieComposition.OnCompositionLoadedListener() {                        @Override                        public void onCompositionLoaded(LottieComposition composition) {                            compositionMap.put(fileName, composition);                            addComposition(composition);                        }                    });        }        return true;    }    private boolean isValidKey(KeyEvent event) {        if (!event.hasNoModifiers()) {            return false;        }        if (event.getKeyCode() >= KeyEvent.KEYCODE_A && event.getKeyCode() <= KeyEvent.KEYCODE_Z) {            string = asciiToString(event.getKeyCode() + 36);            return true;        }        // switch (keyCode) {        //     case KeyEvent.KEYCODE_COMMA:        //     case KeyEvent.KEYCODE_APOSTROPHE:        //     case KeyEvent.KEYCODE_SEMICOLON:        //         return true;        // }        return false;    }    private void addComposition(LottieComposition composition) {        LottieAnimationView lottieAnimationView = new LottieAnimationView(getContext());        lottieAnimationView.setLayoutParams(new LayoutParams(                ViewGroup.LayoutParams.WRAP_CONTENT,                ViewGroup.LayoutParams.WRAP_CONTENT        ));        lottieAnimationView.setComposition(composition);        lottieAnimationView.playAnimation();        if (cursorView == null) {            addView(lottieAnimationView);        } else {            int index = indexOfChild(cursorView);            addView(lottieAnimationView, index);        }    }    private boolean fitsOnCurrentLine(int currentX, View view) {        return currentX + view.getMeasuredWidth() < getWidth() - getPaddingRight();    }    private View createSpaceView() {        View spaceView = new View(getContext());        spaceView.setLayoutParams(new LayoutParams(                getResources().getDimensionPixelSize(R.dimen.font_space_width),                ViewGroup.LayoutParams.WRAP_CONTENT        ));        spaceView.setTag("Space");        return spaceView;    }}

Lottie使用出现问题

  • JSON文件不播放,比如:代码设置文件应用崩溃,布局文件设置了无效。

    • JSON文件有有格式要求的,这点我重复一下,Lottie支持的Json文件来自于Bodymovin,该项目是Adobe公司的动画制作软件After Effects的插件,用来将动画导出成 svg/canvas/html + js ,方便在浏览器上展示。
  • 打开界面就崩:json文件错误。路径是直接跟目录下,直接文件名;根目录的文件夹,是文件夹名/文件名,如:

    • Logo/LogoSmall.json
    • LogoSmall.json
  • loop设置为false 之后,播放结束无法再次开始,请在监听的结束监听中添加:

    animationView.pauseAnimation();
0 0
原创粉丝点击