IT数码 购物 网址 头条 软件 日历 阅读 图书馆
TxT小说阅读器
↓语音阅读,小说下载,古典文学↓
图片批量下载器
↓批量下载图片,美女图库↓
图片自动播放器
↓图片自动播放器↓
一键清除垃圾
↓轻轻一点,清除系统垃圾↓
开发: C++知识库 Java知识库 JavaScript Python PHP知识库 人工智能 区块链 大数据 移动开发 嵌入式 开发工具 数据结构与算法 开发测试 游戏开发 网络协议 系统运维
教程: HTML教程 CSS教程 JavaScript教程 Go语言教程 JQuery教程 VUE教程 VUE3教程 Bootstrap教程 SQL数据库教程 C语言教程 C++教程 Java教程 Python教程 Python3教程 C#教程
数码: 电脑 笔记本 显卡 显示器 固态硬盘 硬盘 耳机 手机 iphone vivo oppo 小米 华为 单反 装机 图拉丁
 
   -> 移动开发 -> 【Android】炫酷ui 带你做一个背景跟着滚动的工具 -> 正文阅读

[移动开发]【Android】炫酷ui 带你做一个背景跟着滚动的工具

? ? ? ? 在用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

  移动开发 最新文章
Vue3装载axios和element-ui
android adb cmd
【xcode】Xcode常用快捷键与技巧
Android开发中的线程池使用
Java 和 Android 的 Base64
Android 测试文字编码格式
微信小程序支付
安卓权限记录
知乎之自动养号
【Android Jetpack】DataStore
上一篇文章      下一篇文章      查看所有文章
加:2021-10-25 12:38:54  更:2021-10-25 12:39:10 
 
开发: C++知识库 Java知识库 JavaScript Python PHP知识库 人工智能 区块链 大数据 移动开发 嵌入式 开发工具 数据结构与算法 开发测试 游戏开发 网络协议 系统运维
教程: HTML教程 CSS教程 JavaScript教程 Go语言教程 JQuery教程 VUE教程 VUE3教程 Bootstrap教程 SQL数据库教程 C语言教程 C++教程 Java教程 Python教程 Python3教程 C#教程
数码: 电脑 笔记本 显卡 显示器 固态硬盘 硬盘 耳机 手机 iphone vivo oppo 小米 华为 单反 装机 图拉丁

360图书馆 购物 三丰科技 阅读网 日历 万年历 2024年11日历 -2024/11/24 0:59:31-

图片自动播放器
↓图片自动播放器↓
TxT小说阅读器
↓语音阅读,小说下载,古典文学↓
一键清除垃圾
↓轻轻一点,清除系统垃圾↓
图片批量下载器
↓批量下载图片,美女图库↓
  网站联系: qq:121756557 email:121756557@qq.com  IT数码