功能介绍
- 使用
Java
编写 - 主要有那些功能?
- ex1:通过seekbar控制lottie动画播放,以及动画自动播放
- ex2:通过下方输入参数控制中间view的动画效果
- ex3:使用tablayout和fragment进行标签页切换,并加入切换后前5s播放lottie动画的功能,动画结束后淡入淡出
效果图
前置技能点
用用java写吧。
Android开发入门http://hukai.me/android-training-course-in-chinese/basics/index.html
java入门https://www.runoob.com/java/java-tutorial.html
Fragment参考
• 官⽅⽂档:
https://developer.android.com/guide/fragments
• 中⽂翻译:
https://juejin.cn/post/6900739309826441224#heading-29
https://juejin.cn/post/6901453354463920135
• Fragment 源码解读:
https://juejin.cn/post/6844904086437904398
Animation参考
• 属性动画:
https://developer.android.com/guide/topics/graphics/prop-animation
• ⻉塞尔曲线可视化:
https://cubic-bezier.com/
• ⾮官⽅总结:
https://juejin.cn/post/6844903465211133959
• 其他阅读材料:属性动画
https://rengwuxian.com/123.html
https://rengwuxian.com/127.html
• Lottie Android 指南:
https://airbnb.io/lottie/#/android
• Lottie 资源:
https://lottiefiles.com/
本项目地址在本人github
项目结构
项目是老师给的模板中融合了我参考Android Studio的Tabbed Activity模板添加的文件。文件比较多,只列了部分,也只挑其中重要的讲。
Java文件
从上到下依次为:
- 1-3 三个按键对应的activity类
- 4 主界面活动,控制三个按键逻辑,进入三个不同的activity
- 5 这个名字取得不太好,因为是直接照搬的我上一篇教程。RecyclerView的自定义Adapter类,主要是规定了如何装载和控制视图内容,包括item的点击事件,这次把ViewHolder也放在里面了
- 6 写这个的时候发现并不需要这个类,这个类是默认模板控制每个页面view内容的
- 7 调用SectionsPagerAdapter的getItem时需要来实例化给定页面的fragment实例,通过PlaceholderFragment.newInstance实现
- 8 自定义的view类型,可以制作彩虹文字
- 9 自定义的SectionsPagerAdapter,继承自FragmentPagerAdapter,控制整个Fragment的显示效果,包括tab的文字,总页面数量等
Layout文件
分别为对应的activity的布局,不详细解释了,说一下要注意的点。
xml添加lottie动画
如何添加lottie动画在前置技能点有,在本app中,需要在ex1.xml和fragment_placeholder.xml中加入(当然id注意不要重复)
<com.airbnb.lottie.LottieAnimationView android:id="@+id/animation_view" android:layout_width="wrap_content" android:layout_height="wrap_content" android:layout_gravity="center" app:lottie_loop="true" app:lottie_rawRes="@raw/material_wave_loading" />
其中app:lottie_rawRes指定了json格式文件的动画文件位置,一般放在raw文件夹下。
fragment_placeholder.xml中只有animation_view2和recycler_view,每个fragment片段就是这两个view淡入淡出实现的
功能实现
勾选checkbox切换动画播放状态
这里的loopCheckBox是一个CheckBox类型
loopCheckBox.setOnCheckedChangeListener(new CompoundButton.OnCheckedChangeListener() { @Override public void onCheckedChanged(CompoundButton buttonView, boolean isChecked) { if (isChecked) { // 当选中自动播放的时候,开始播放 lottie 动画,同时禁止手动修改进度 animationView.playAnimation(); seekBar.setEnabled(false); } else { // 当去除自动播放时,停止播放 lottie 动画,同时允许手动修改进度 animationView.pauseAnimation(); seekBar.setEnabled(true); } } });
seekbar控制动画进度
这里的seekBar是一个SeekBar类型,已通过findViewById(R.id.seekbar)绑定
seekBar.setOnSeekBarChangeListener(new SeekBar.OnSeekBarChangeListener() { @Override public void onProgressChanged(SeekBar seekBar, int progress, boolean fromUser) { // TODO ex1-2: 这里应该调用哪个函数呢 // 提示1:可以参考 https://airbnb.io/lottie/#/android?id=custom-animators // 提示2:SeekBar 的文档可以把鼠标放在 OnProgressChanged 中间,并点击 F1 查看, // 或者到官网查询 https://developer.android.google.cn/reference/android/widget/SeekBar.OnSeekBarChangeListener.html#onProgressChanged(android.widget.SeekBar,%20int,%20boolean // 每次progress改变的时候都会调用这个函数,所以只需要在这时候将新的progress对应的百分比传入即可 // progress默认是1-100的整数,用getMax()最理想 animationView.setProgress((float)progress/seekBar.getMax()); }
ex2中的一些实现
选颜色是通过给颜色view添加一个点击监听,通过ColorPicker类展示的选择颜色窗口
复杂的动画效果是通过AnimatorSet串联了多个动画实现的,其中控制颜色使用的是ObjectAnimator.ofArgb()实现的,其他的是ofFloat()
ex3的一些实现
主活动如下
public class Ch3Ex3Activity extends AppCompatActivity { @Override protected void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); setContentView(R.layout.activity_ch3ex3); // TODO: ex3-1. 添加 ViewPager 和 Fragment 做可滑动界面 SectionsPagerAdapter sectionsPagerAdapter = new SectionsPagerAdapter(this, getSupportFragmentManager()); ViewPager viewPager = findViewById(R.id.view_pager); viewPager.setAdapter(sectionsPagerAdapter); // TODO: ex3-2, 添加 TabLayout 支持 Tab TabLayout tabs = findViewById(R.id.tabs); tabs.setupWithViewPager(viewPager); } }
SectionsPagerAdapter类
public class SectionsPagerAdapter extends FragmentPagerAdapter { @StringRes private static final int[] TAB_TITLES = new int[]{R.string.tab_text_1, R.string.tab_text_2, R.string.tab_text_3}; private final Context mContext; public SectionsPagerAdapter(Context context, FragmentManager fm) { super(fm); mContext = context; } @Override public Fragment getItem(int position) { // getItem is called to instantiate the fragment for the given page. // Return a PlaceholderFragment (defined as a static inner class below). return PlaceholderFragment.newInstance(position); } @Nullable @Override public CharSequence getPageTitle(int position) { return mContext.getResources().getString(TAB_TITLES[position]); } @Override public int getCount() { // Show 3 total pages. return 3; } }
PlaceholderFragment类
public class PlaceholderFragment extends Fragment { private LottieAnimationView animationView2; private RecyclerView myRecycler; private Context mContext; private List list = new ArrayList(); private static final String ARG_SECTION_NUMBER = "tab_number"; public static PlaceholderFragment newInstance(int index) { PlaceholderFragment fragment = new PlaceholderFragment(); Bundle bundle = new Bundle(); bundle.putInt(ARG_SECTION_NUMBER, index); fragment.setArguments(bundle); return fragment; } @Override public View onCreateView(@NonNull LayoutInflater inflater, ViewGroup container, Bundle savedInstanceState) { // TODO ex3-3: 修改 fragment_placeholder,添加 loading 控件和列表视图控件 View root = inflater.inflate(R.layout.fragment_placeholder, container, false); animationView2 = root.findViewById(R.id.animation_view2); myRecycler = root.findViewById(R.id.recycler_view); mContext = container.getContext(); return root; } @Override public void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); // 初始化数组 for (int i = 1; i < 101; i++) { list.add(String.format("这里是第 %d 行", i)); } } @Override public void onActivityCreated(@Nullable Bundle savedInstanceState) { super.onActivityCreated(savedInstanceState); // 播放动画 animationView2.playAnimation(); // 展示 recycler view myRecycler.setLayoutManager(new LinearLayoutManager(mContext)); myRecycler.setAdapter(new MyAdapter(list)); myRecycler.setVisibility(View.GONE); //前5s并不显示不渲染 ObjectAnimator fadeOutAnimator = ObjectAnimator.ofFloat(animationView2, "alpha", 1f, 0f);//淡出效果,alpha从1到0 fadeOutAnimator.setDuration(1000);// 淡出1s // fadeOutAnimator.setRepeatCount(0); // 设置动画重复播放次数 = 重放次数+1 ObjectAnimator fadeInAnimator = ObjectAnimator.ofFloat(myRecycler, "alpha", 0f, 1f); fadeInAnimator.setDuration(1000); // fadeInAnimator.addUpdateListener(new ValueAnimator.AnimatorUpdateListener() { // // 如果fadeInAnimator = ValueAnimator.ofInt(0,255) ,也可以使用这个 // @Override // public void onAnimationUpdate(ValueAnimator animation) { // int curValue = (int)animation.getAnimatedValue(); // myRecycler.setAlpha((float)curValue/255); // Log.d("now alpha:" + myRecycler.getAlpha()," curValue:"+curValue); // } // }); // 丢到一个动画集合里,一起运行 final AnimatorSet fadeInOut = new AnimatorSet(); fadeInOut.playTogether(fadeInAnimator,fadeOutAnimator); getView().postDelayed(new Runnable() { @Override public void run() { // 这里会在 5s 后执行 // TODO ex3-4:实现动画,将 lottie 控件淡出,列表数据淡入 myRecycler.setAlpha(0f); //先设置透明度为0再显示 myRecycler.setVisibility(View.VISIBLE); fadeInOut.start(); } }, 5000); // 如果不需要等5s设置透明度,可以用这个delay // fadeInOut.setStartDelay(5000); // fadeInOut.start(); } }
主要的坑是
- setRepeatCount()设置的是重复次数而不是运行次数,如果设置为1,是运行两次
- 对于一个想要延迟淡入的view,首先要设置visibility为GONE,然后在即将淡入前设置透明度为0,visibility为VISIBLE
说明
本例中的不同fragment切换时的动画有时有,有时没有。
这是因为fragment默认是预加载下一个的,所以tab1和2都加载了,再进入tab2时没有加载动画,而到tab3就有动画了。
默认的预加载如果想要取消,需要自定义一个判断当前fragment是否可见的子类,参考https://www.jianshu.com/p/0e2d746e3a3d