Lottie要求最低编辑版本是16(Android4.1)
minSdkVersion 16
Gradle注册添加支持
dependencies {
compile 'com.airbnb.android:lottie:1.0.1'
}
给使用到该控件的布局文件根标签添加(如果你在布局文件设置的话,如果没有,请忽略)
xmlns:app="http://schemas.android.com/apk/res-auto"
布局文件
/**
* lottie_fileName json文件名
* lottie_loop 是否循环播放
* lottie_autoPlay 是否自动播放
代码实现
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();
}
在这里接收选择的文件
@Override
protected 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的滑动监听,他之所以用那个是为了美观。如果谁有兴趣,可以使用一下试试看。
布局文件使用RelativeLayout,在 LottieAnimationView 上面添加一个ViewPager
代码方面设置监听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 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 compositionMap = new HashMap<>();
private final List 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;
}
}
JSON文件不播放,比如:代码设置文件应用崩溃,布局文件设置了无效。
打开界面就崩:json文件错误。路径是直接跟目录下,直接文件名;根目录的文件夹,是文件夹名/文件名,如:
loop设置为false 之后,播放结束无法再次开始,请在监听的结束监听中添加:
animationView.pauseAnimation();