Java安卓开发入门-TabLayout、Fragment和属性动画
功能介绍
- 使用
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

浙公网安备 33010602011771号