在用ViewPager配合Fragment開發的模式中,想做一個類似于桌面壁紙的背景圖,可以跟著ViewPager滑動,這里貼一下專案初期實作了的效果:

可以看到,界面中ViewPager滑動的同時背景圖片也跟著滑動了,二者的滑動速率不一樣,那么來
大體思路:
在ViewPager滑動的程序中,監聽滑動百分比,再通過這個滑動的百分比來控制背景圖的偏移,背景圖的偏移通過背景圖的尺寸和View容器的尺寸來計算,最后將這個偏移后的圖片顯示在ImageView或者某個View的Drawable上,(其實SurfaceView的性能會強得多,但是SurfaceView沒有View屬性,而且放在布局中還會讓其他View的顯示出現一些問題,特別是有半透明,陰影這些地方)
那么接下來就開始吧,首先準備好一張背景圖片,一個ViewPager,作者用的是VIewPager2,
這里先放一個簡單的一個ViewPager2,
ViewPagerAdapter.java
import android.view.LayoutInflater;
import android.view.View;
import android.view.ViewGroup;
import android.widget.TextView;
import androidx.annotation.NonNull;
import androidx.recyclerview.widget.RecyclerView;
import org.jetbrains.annotations.NotNull;
/**
* @author ldh
* 時間: 2021/10/23 13:51
* 郵箱: 2637614077@qq.com
*/
public class ViewPagerAdapter extends RecyclerView.Adapter<ViewPagerAdapter.MyViewHolder> {
int maxCount = 0;
public ViewPagerAdapter(int maxCount){
this.maxCount = maxCount;
}
@NonNull
@NotNull
@Override
public MyViewHolder onCreateViewHolder(@NonNull @NotNull ViewGroup parent, int viewType) {
return new MyViewHolder(LayoutInflater.from(parent.getContext()).inflate(R.layout.item_viewpager, parent, false));
}
@Override
public void onBindViewHolder(@NonNull @NotNull MyViewHolder holder, int position) {
holder.textView.setText("這是第" + (position + 1) + "頁");
}
@Override
public int getItemCount() {
return maxCount;
}
class MyViewHolder extends RecyclerView.ViewHolder {
TextView textView;
public MyViewHolder(@NonNull @NotNull View itemView) {
super(itemView);
textView = itemView.findViewById(R.id.item_viewpager_textview);
}
}
}
MainActivity.kt
val viewpager2 = findViewById<ViewPager2>(R.id.viewpager2)
viewpager2.adapter = ViewPagerAdapter(4)
現在是這個效果,此時只是簡單的完成了一個ViewPager,

然后我們寫一個工具類,用偏移比率來描述偏移量,
偏移比率:[0,1]之間,初始為0,0表示還沒開始劃,1就是已經劃完了,因為圖片的尺寸和view的尺寸是可能會隨著需求動態變化的,所以只記錄偏移的比例,
ImageScroller.kt
import android.graphics.Bitmap
import android.graphics.BitmapFactory
import android.graphics.Matrix
import android.os.Looper
import android.util.Log
import android.widget.ImageView
import kotlin.math.roundToInt
/**
* @author ldh
* 時間: 2021/10/23 14:09
* 郵箱: 2637614077@qq.com
*/
class ImageScroller {
private var bitmap: Bitmap? = null
private val matrix = Matrix()
/**
* 分別是水平方向和豎直方向的偏移比率
*/
private var offsetRateVertical: Float = 0f
private var offsetRateHorizontal: Float = 0f
/**
* 關聯的ImageView,如果ImageView為空,則設定View的Drawable,否則直接讓ImageView顯示圖片
*/
private var imageView: ImageView? = null
/**
* 容器的尺寸,也就是view的寬和高
*/
private var containerHeight: Int = 0
private var containerWidth: Int = 0
/**
* 圖片的尺寸(經過拉伸后的)
*/
private var imageHeight: Int = 0
private var imageWidth: Int = 0
companion object {
/**
* 滾動方向,默認為水平滾動
*/
const val SCROLL_MODE_VERTICAL = 0
const val SCROLL_MODE_HORIZONTAL = 1
}
private var scrollMode: Int = SCROLL_MODE_HORIZONTAL
/**
* 計算圖片資訊等相關引數
*/
private fun measure() {
bitmap?.let {
if (containerHeight != 0 && containerWidth != 0) {
//如果容器尺寸還沒算,這里就沒有意義了,所以加個判斷
var scale = 0f;
if (scrollMode == SCROLL_MODE_HORIZONTAL) {
//水平滾動模式
//使圖片的高度拉伸到圖片的高度
//如果拉伸完過后圖片的寬度比容器的寬度還小,那就使圖片寬度拉伸到螢屏寬度,不然就會形成空白區域
scale = containerHeight / (it.height).toFloat()
val width = it.width * scale;
if (width < containerWidth) {
scale = containerWidth / (it.width).toFloat()
}
} else {
//豎直滾動模式,邏輯同理
scale = containerWidth / (it.width).toFloat()
val height = it.height * scale;
if (height < containerHeight) {
scale = containerHeight / (it.height).toFloat()
}
}
imageWidth = (it.width * scale).roundToInt()
imageHeight = (it.height * scale).roundToInt()
matrix.setScale(scale, scale)
matrix.postTranslate(
offsetRateHorizontal * (containerWidth - imageWidth),
offsetRateVertical * (containerHeight - imageHeight))
draw()
}
}
}
/**
* 設定圖片,設定圖片之后要計算引數
*/
fun setBitmap(bitmap: Bitmap){
this.bitmap = bitmap
imageView?.setImageBitmap(bitmap)
imageView?.scaleType = ImageView.ScaleType.MATRIX
measure()
}
fun isVerticalScrollMode() = (scrollMode == SCROLL_MODE_VERTICAL)
fun isHorizontalScrollMode() = (scrollMode == SCROLL_MODE_HORIZONTAL)
/**
* 計算容器的尺寸
* 計算完過后還要計算一遍measure()
*/
private fun measureContainer() {
imageView?.post {
containerHeight = imageView!!.height
containerWidth = imageView!!.width
measure()
}
}
fun setupWithImageView(imageView: ImageView){
if(this.imageView != null && this.imageView != imageView){
return
}
imageView.scaleType = ImageView.ScaleType.MATRIX
this.imageView = imageView
bitmap?.let {
setBitmap(it)
}
measureContainer()
}
fun setHorizontalScrollMode(){
setScrollMode(SCROLL_MODE_HORIZONTAL)
}
fun setVerticalScrollMode(){
setScrollMode(SCROLL_MODE_VERTICAL)
}
fun setScrollMode(scrollMode: Int){
//每次發生切換的時候要重新measure()一次
if (scrollMode == SCROLL_MODE_VERTICAL && this.scrollMode == SCROLL_MODE_HORIZONTAL){
//從水平滾動切換到豎直滾動
this.scrollMode = SCROLL_MODE_VERTICAL
measure()
}
if(this.scrollMode == SCROLL_MODE_VERTICAL && scrollMode == SCROLL_MODE_HORIZONTAL){
//從豎直滾動切換到滾動水平
this.scrollMode = SCROLL_MODE_HORIZONTAL
measure()
}
}
/**
* 增加或減少偏移率Y
*/
fun varOffsetVerticalRate(offsetY: Float){
this.offsetRateVertical += offsetY;
matrix.postTranslate(0f, (containerHeight - imageHeight) * offsetY)
draw()
}
fun setOffsetRate(offsetRate: Float) {
//如果直接用set,那scale屬性就沒了
matrix.postTranslate(-(containerWidth - imageWidth) * offsetRateHorizontal, -(containerHeight - imageHeight) * offsetRateVertical)
if(isVerticalScrollMode()){
offsetRateVertical = offsetRate
}else {
offsetRateHorizontal = offsetRate
}
matrix.postTranslate((containerWidth - imageWidth) * offsetRateHorizontal, (containerHeight - imageHeight) * offsetRateVertical)
draw()
}
/**
* 增加或減少偏移率X
*/
fun varOffsetHorizontalRate(offsetX: Float){
this.offsetRateHorizontal += offsetX;
matrix.postTranslate( (containerWidth - imageWidth) * offsetX, 0f)
draw()
}
fun draw(){
//因為不能在子執行緒更新UI,所以每次要判斷當前是否在主執行緒中
if(isMainThread()){
if(imageView != null){
imageView!!.setImageMatrix(matrix)
}
}else{
//如果是子執行緒呼叫的,那就發送到主執行緒去
imageView?.post {
draw()
}
}
}
/**
* 判斷當前程式是否在主執行緒中運行
*/
fun isMainThread(): Boolean {
return Thread.currentThread() === Looper.getMainLooper().thread
}
constructor()
constructor(imageView: ImageView){
setupWithImageView(imageView)
}
constructor(imageView: ImageView,bitmap: Bitmap){
setupWithImageView(imageView)
setBitmap(bitmap)
}
constructor(imageView: ImageView, id: Int){
setupWithImageView(imageView)
setBitmap(BitmapFactory.decodeResource(imageView.context.resources, id))
}
}
再寫一個工具,用來系結ViewPager和ImageScroller,作者在這里只寫了一個ViewPager2的系結器,其他的可以自己寫,這里通過判斷ViewPager2是豎直滑動還是水平滑動來動態改變ImageScroller的滑動模式
BindUtils.kt
import androidx.recyclerview.widget.RecyclerView
import androidx.viewpager2.widget.ViewPager2
import java.util.concurrent.Executors
/**
* @author ldh
* 時間: 2021/10/23 18:25
* 郵箱: 2637614077@qq.com
*/
object BindUtils {
fun bindWithViewPager2(viewPager2: ViewPager2?, imageScroller: ImageScroller?) {
if(viewPager2 != null && imageScroller != null) {
if(viewPager2.orientation == RecyclerView.HORIZONTAL){
//viewPager是水平滑動,那設定ImageScroller也是水平滑動
imageScroller.setHorizontalScrollMode()
}else {
imageScroller.setVerticalScrollMode()
}
viewPager2.registerOnPageChangeCallback(object : ViewPager2.OnPageChangeCallback() {
//這里用子執行緒控制Image滑動,如果用主執行緒的話容易造成滑動影片卡頓掉幀
val executorService = Executors.newSingleThreadExecutor();
override fun onPageScrolled(position: Int, positionOffset: Float, positionOffsetPixels: Int) {
super.onPageScrolled(position, positionOffset, positionOffsetPixels)
executorService.execute {
imageScroller.setOffsetRate((position + positionOffset) / ((viewPager2.adapter as RecyclerView.Adapter).itemCount - 1))
}
}
})
}
}
}
最后再貼一下MainActivity的代碼
MainActivity.kt
import android.os.Bundle
import android.widget.ImageView
import androidx.appcompat.app.AppCompatActivity
import androidx.viewpager2.widget.ViewPager2
class MainActivity : AppCompatActivity() {
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
setContentView(R.layout.activity_main)
supportActionBar?.hide()
val viewpager1 = findViewById<ViewPager2>(R.id.viewpager1)
viewpager1.adapter = ViewPagerAdapter(4)
val imageScroller1 = ImageScroller(findViewById<ImageView>(R.id.imageView1), R.drawable.color_clouds)
BindUtils.bindWithViewPager2(viewpager1, imageScroller1)
val viewpager2 = findViewById<ViewPager2>(R.id.viewpager2)
viewpager2.adapter = ViewPagerAdapter(4)
val imageScroller2 = ImageScroller(findViewById<ImageView>(R.id.imageView2), R.drawable.image_long)
BindUtils.bindWithViewPager2(viewpager2, imageScroller2)
}
}
activity_main.xml
<?xml version="1.0" encoding="utf-8"?>
<LinearLayout
xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:tools="http://schemas.android.com/tools"
android:orientation="vertical"
android:layout_width="match_parent"
android:layout_height="match_parent"
tools:context=".MainActivity">
<RelativeLayout
android:layout_marginHorizontal="80dp"
android:layout_marginVertical="10dp"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:layout_weight="1">
<ImageView
android:layout_width="match_parent"
android:layout_height="match_parent"
android:id="@+id/imageView1"/>
<androidx.viewpager2.widget.ViewPager2
android:orientation="horizontal"
android:id="@+id/viewpager1"
android:layout_width="match_parent"
android:layout_height="match_parent"
/>
</RelativeLayout>
<RelativeLayout
android:layout_marginHorizontal="80dp"
android:layout_marginVertical="10dp"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:layout_weight="1">
<ImageView
android:layout_width="match_parent"
android:layout_height="match_parent"
android:id="@+id/imageView2"/>
<androidx.viewpager2.widget.ViewPager2
android:orientation="vertical"
android:id="@+id/viewpager2"
android:layout_width="match_parent"
android:layout_height="match_parent"
/>
</RelativeLayout>
</LinearLayout>
實作出來的效果:


原理其實很簡單,就是監聽ViewPager2的滑動,根據滑動百分比來設定圖片的偏移,核心代碼就兩個檔案:ImageScroller.kt和BindUtils.kt,demo我已經上傳到碼云上了,
本專案地址:ImageScroller
轉載請註明出處,本文鏈接:https://www.uj5u.com/yidong/336269.html
標籤:其他
上一篇:GitHub上有38.8K 的stars Android 開源庫你知道是那個嗎?
下一篇:Koltin實作動態心率曲線
