前言
作为一个很久没写过 Android 业务的人,心里有点慌了,于是拿起 Android Studio,还是找点东西学习一下,并且记录一下。一直觉得 ViewPager 是个好东西,偶然间看到一些很好的案例,很酷炫的翻页效果。直到了解了这个东西的实现原来没有想象中的那么复杂,但如果没有深刻理解,还是很难写出酷炫的效果的。于是, ViewPager Transformer 的学习就提上了日程。
Hello World
首先我们来实现一个场景,很简单,只需要一个 ViewPager,然后给他设置几页用来展现效果就行了
Layout 文件
只需要放入一个 ViewPager
<?xml version="1.0" encoding="utf-8"?>
<androidx.constraintlayout.widget.ConstraintLayout
xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:tools="http://schemas.android.com/tools"
xmlns:app="http://schemas.android.com/apk/res-auto"
android:layout_width="match_parent"
android:layout_height="match_parent"
tools:context=".MainActivity">
<androidx.viewpager.widget.ViewPager
android:layout_width="match_parent"
android:layout_height="match_parent"
android:id="@+id/view_pager">
</androidx.viewpager.widget.ViewPager>
</androidx.constraintlayout.widget.ConstraintLayout>
之后,我们创建一个 PageAdapter,可以直接使用 FragmentPagerAdapter,getItem 返回一个 Fragment 就好了
class PageAdapter(fm: FragmentManager) : FragmentPagerAdapter(fm) {
override fun getItem(position: Int): Fragment {
return PageFragment("Fragment $position", position)
}
override fun getCount(): Int {
return 4
}
@SuppressLint("ValidFragment")
class PageFragment(private var content: String, private var position: Int) : Fragment() {
private val colors = Arrays.asList(Color.GRAY, Color.RED, Color.BLUE, Color.YELLOW)!!
override fun onActivityCreated(savedInstanceState: Bundle?) {
super.onActivityCreated(savedInstanceState)
text_view.text = content
}
override fun onCreateView(
inflater: LayoutInflater,
container: ViewGroup?,
savedInstanceState: Bundle?
): View? {
val view = inflater.inflate(R.layout.tab_item, container, false)
view.setBackgroundColor(colors[position % colors.size])
view.tag = "$position"
return view
}
}
}
在我们的 Activity 中,设置 view_pager,这里需要注意的是,由于我们使用了 FragmentPagerAdapter,所以我们在展示是如果需要展示多页的话,必须设置为 offscreenPageLimit 一个比较大的值,以便 ViewPager 能够渲染足够多的页面满足我们的需求。
最后为 ViewPager 设置一个 PageTransformer
class MainActivity : AppCompatActivity() {
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
setContentView(R.layout.activity_main)
view_pager.adapter = PageAdapter(supportFragmentManager)
view_pager.offscreenPageLimit = 4
view_pager.setPageTransformer(true, ViewPagerTransformer(TransformType.DEPTH))
}
}
一个比较简单的 PageTransformer 的实现如下
class ViewPagerTransformer : ViewPager.PageTransformer {
override fun transformPage(page: View, position: Float) {
page.rotationY = position * -30f
}
}
效果如下,翻页时会根据位置修改页面的显示,页面将会绕 Y 轴进行旋转一定的角度,效果很赞吧!!!只用了一点代码
ViewPager.PageTransformer
定义
PageTransfomer 接口只有一个方法,该方法有两个参数,一个是 page,指的是 ViewPage 的一个内容页
public interface PageTransformer {
/**
* Apply a property transformation to the given page.
*
* @param page Apply the transformation to this page
* @param position Position of page relative to the current front-and-center
* position of the pager. 0 is front and center. 1 is one full
* page position to the right, and -1 is one page position to the left.
*/
void transformPage(@NonNull View page, float position);
}
position 指的是该内容页的位置偏移,该偏移是相对的,具体表示请看一张图,页面静止时,以屏幕左边界为 0,屏幕内的页面 position 为0,左边为-1,依次递减,右侧为1,依次递增。当屏幕滑动时,page2只出现一半,此时,page2 的 position 为-0.5,page3 为0.5,依次类推可得出其他page 回调的 position 值
实践
1、淡入淡出 效果
页面随着位置改变透明度,alpha = 0 是透明,alpha = 1 是不透明
if (position <= -1.0f || position >= 1.0f) {
page.alpha = 0.0f
} else if (position == 0.0f) {
page.alpha = 1.0f
} else {
page.alpha = 1.0f - Math.abs(position)
}
2、缩放变大效果
同时改变位移与透明度
if (position > 0 && position < 1) {
page.alpha = 1 - position
page.scaleXY = 0.85f + (1 - 0.85f) * (1 - Math.abs(position))
page.translationX = page.width * -position
} else {
page.alpha = 1f
page.scaleXY = 1f
page.translationX = 0f
}
更多效果
等你去发现
源码解析
其实这个原理很简单,在每一次滚动的时候,在 ViewPager 内部,计算出 每一个view 的 position ,并且调用这个接口的方法就可以实现了
源码如下
protected void onPageScrolled(int position, float offset, int offsetPixels) {
// 省略.......
if (mPageTransformer != null) {
final int scrollX = getScrollX();
final int childCount = getChildCount();
for (int i = 0; i < childCount; i++) {
final View child = getChildAt(i);
final LayoutParams lp = (LayoutParams) child.getLayoutParams();
if (lp.isDecor) continue;
final float transformPos = (float) (child.getLeft() - scrollX) / getClientWidth();
mPageTransformer.transformPage(child, transformPos);
}
}
// ....
}
首先判断 mPageTransformer 是否存在,存在的话就可以调用了,获取 scrollX,根据 childCount 对每一个 view 执行 mPageTransformer.transformPage 方法 transformPos 是由 (float) (child.getLeft() - scrollX) / getClientWidth() 计算得出。此处使用 getLeft - scrollX 计算验证了我们对想法。
总结
看似复杂的功能,其实没那么复杂,静下心来研究,原来这么简单