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 小米 华为 单反 装机 图拉丁
 
   -> 移动开发 -> 2022-01-28 Android app 背景图首尾相接滚动效果 -> 正文阅读

[移动开发]2022-01-28 Android app 背景图首尾相接滚动效果

一、运行效果图如下,背景图可以上下左右四个方向滚动。

二、直接上源码。

1、

?2、src\main\java\com\giada\healthcode\MainActivity.java

package com.giada.healthcode;

import androidx.appcompat.app.AppCompatActivity;

import android.os.Bundle;
import android.view.View;
import android.view.WindowManager;
import android.widget.Button;
import android.widget.TextView;

public class MainActivity extends AppCompatActivity {

    private Button btn_start,btn_stop,btn_change,btn_speedup;
    private SrcScrollFrameLayout fl_main;
    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        getWindow().addFlags(WindowManager.LayoutParams.FLAG_TRANSLUCENT_STATUS);
        setContentView(R.layout.activity_main);

        btn_start    = (Button) findViewById(R.id.btn_start);
        btn_stop     = (Button) findViewById(R.id.btn_stop);
        btn_change   = (Button) findViewById(R.id.btn_change);
        btn_speedup  = (Button) findViewById(R.id.btn_speedup);

        fl_main = (SrcScrollFrameLayout) findViewById(R.id.fl_main);
        btn_start.setOnClickListener(new View.OnClickListener() {
            @Override
            public void onClick(View v) {
                fl_main.startScroll();
                fl_main.DEFAULT_DRAW_INTERVALS_TIME=10;
            }
        });
        btn_stop.setOnClickListener(new View.OnClickListener() {
            @Override
            public void onClick(View v) {
                fl_main.stopScroll();
            }
        });

        btn_change.setOnClickListener(new View.OnClickListener() {
            @Override
            public void onClick(View v) {
                fl_main.changeScrollOrientation();
            }
        });
        btn_speedup.setOnClickListener(new View.OnClickListener() {
            @Override
            public void onClick(View v) {
                fl_main.DEFAULT_DRAW_INTERVALS_TIME -= 10;
            }
        });

    }
}

??3、src\main\java\com\giada\healthcode\SrcScrollFrameLayout.java 核心代码

package com.giada.healthcode;

import android.content.Context;
import android.content.res.TypedArray;
import android.graphics.Bitmap;
import android.graphics.Canvas;
import android.graphics.Color;
import android.graphics.Matrix;
import android.graphics.Paint;
import android.graphics.drawable.BitmapDrawable;
import android.graphics.drawable.Drawable;
import android.util.AttributeSet;
import android.widget.FrameLayout;

import androidx.annotation.ColorInt;
import androidx.annotation.IntDef;
import androidx.annotation.NonNull;
import androidx.annotation.Nullable;

import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;

/**
 * PackageName : com.ziwenl.library.widgets
 * Author : Ziwen Lan
 * Date : 2020/5/13
 * Time : 11:23
 * Introduction :仿小红书登陆页面背景图无限滚动 FrameLayout
 * 功能特点:
 * 1.将选择的图片按比例缩放填满当前 View 高度
 * 2.背景图片缩放后宽/高度小于当前 View 宽/高度时自动复制黏贴直到占满当前 View 宽/高度,以此来达到无限滚动效果
 * 3.可通过自定义属性 speed 调整滚动速度,提供 slow、ordinary 和 fast 选项,也可自行填入 int 值,值越大滚动速度越快,建议 1 ≤ speed ≤ 50
 * 4.可通过自定义属性 maskLayerColor 设置遮罩层颜色,建议带透明度
 * 5.提供 startScroll 和 stopScroll 方法控制开始/停止滚动
 * 6.可通过自定义属性 scrollOrientation 设置滚动方向,可设置为上移、下移、左移或右移
 *
 * @Deprecated 建议使用最新的 kotlin 版 {@link SrcLoopScrollFrameLayout},后续 Java 版本可能将放弃维护
 */
@Deprecated
public class SrcScrollFrameLayout extends FrameLayout {
    /**
     * 滚动方向
     * 0:往上滚出
     * 1:往下滚出
     * 2:往左滚出
     * 3:往右滚出
     */
    public final static int OUT_SLIDE_TOP = 0;
    public final static int OUT_SLIDE_BOTTOM = 1;
    public final static int OUT_SLIDE_LEFT = 2;
    public final static int OUT_SLIDE_RIGHT = 3;

    @IntDef({OUT_SLIDE_TOP, OUT_SLIDE_BOTTOM, OUT_SLIDE_LEFT, OUT_SLIDE_RIGHT})
    @Retention(RetentionPolicy.SOURCE)
    public @interface ScrollOrientation {
    }

    /**
     * 重绘间隔时间
     */
    //private final static long DEFAULT_DRAW_INTERVALS_TIME = 5L;
    public long DEFAULT_DRAW_INTERVALS_TIME = 100L;
    /**
     * 间隔时间内平移距离
     */
    private float mPanDistance = 0;
    /**
     * 间隔时间内平移增距
     */
    private float mIntervalIncreaseDistance = 0.5f;
    /**
     * 填满当前view所需bitmap个数
     */
    private int mBitmapCount = 0;
    /**
     * 是否开始滚动
     */
    private boolean mIsScroll;
    /**
     * 滚动方向,默认往上滚出
     */
    @ScrollOrientation
    private int mScrollOrientation;
    /**
     * 遮罩层颜色
     */
    @ColorInt
    private int mMaskLayerColor;

    private Drawable mDrawable;
    private Bitmap mSrcBitmap;
    private Paint mPaint;
    private Matrix mMatrix;

    public SrcScrollFrameLayout(@NonNull Context context) {
        this(context, null, 0);
    }

    public SrcScrollFrameLayout(@NonNull Context context, @Nullable AttributeSet attrs) {
        this(context, attrs, 0);
    }

    public SrcScrollFrameLayout(@NonNull Context context, @Nullable AttributeSet attrs, int defStyleAttr) {
        super(context, attrs, defStyleAttr);

        TypedArray array = context.getTheme().obtainStyledAttributes(attrs, R.styleable.SrcScrollFrameLayout, defStyleAttr, 0);
        int speed = array.getInteger(R.styleable.SrcScrollFrameLayout_speed, 3);
        mScrollOrientation = array.getInteger(R.styleable.SrcScrollFrameLayout_scrollOrientation, OUT_SLIDE_TOP);
        mIntervalIncreaseDistance = speed * mIntervalIncreaseDistance;
        mDrawable = array.getDrawable(R.styleable.SrcScrollFrameLayout_src);
        mIsScroll = array.getBoolean(R.styleable.SrcScrollFrameLayout_isScroll, true);
        mMaskLayerColor = array.getColor(R.styleable.SrcScrollFrameLayout_maskLayerColor, Color.TRANSPARENT);
        array.recycle();

        setWillNotDraw(false);
        mPaint = new Paint();
        mMatrix = new Matrix();

    }

    @Override
    protected void onSizeChanged(int w, int h, int oldw, int oldh) {
        super.onSizeChanged(w, h, oldw, oldh);
        if (mDrawable == null || !(mDrawable instanceof BitmapDrawable)) {
            return;
        }
        if (getVisibility() == GONE) {
            return;
        }
        if (w == 0 || h == 0) {
            return;
        }
        if (mSrcBitmap == null) {
            Bitmap bitmap = ((BitmapDrawable) mDrawable).getBitmap();
            //调整色彩模式进行质量压缩
            Bitmap compressBitmap = bitmap.copy(Bitmap.Config.RGB_565, true);
            //缩放 Bitmap
            mSrcBitmap = scaleBitmap(compressBitmap);
            //计算至少需要几个 bitmap 才能填满当前 view
            mBitmapCount = scrollOrientationIsVertical() ?
                    getMeasuredHeight() / mSrcBitmap.getHeight() + 1
                    :
                    getMeasuredWidth() / mSrcBitmap.getWidth() + 1;
            if (!compressBitmap.isRecycled()) {
                compressBitmap.isRecycled();
                System.gc();
            }
        }
    }

    @Override
    protected void onDraw(Canvas canvas) {
        super.onDraw(canvas);
        if (mSrcBitmap == null) {
            return;
        }
        int length = scrollOrientationIsVertical() ? mSrcBitmap.getHeight() : mSrcBitmap.getWidth();
        int measuredHeight = getMeasuredHeight();
        int measuredWidth = getMeasuredWidth();
        if (length + mPanDistance != 0) {
            //第一张图片未完全滚出屏幕
            mMatrix.reset();
            switch (mScrollOrientation) {
                case OUT_SLIDE_TOP:
                    mMatrix.postTranslate(0, mPanDistance);
                    break;
                case OUT_SLIDE_BOTTOM:
                    mMatrix.postTranslate(0f, measuredHeight - length - mPanDistance);
                    break;
                case OUT_SLIDE_LEFT:
                    mMatrix.postTranslate(mPanDistance, 0);
                    break;
                case OUT_SLIDE_RIGHT:
                    mMatrix.postTranslate(measuredWidth - length - mPanDistance, 0f);
                    break;

            }
            canvas.drawBitmap(mSrcBitmap, mMatrix, mPaint);
        }
        if (length + mPanDistance < (scrollOrientationIsVertical() ? measuredHeight : measuredWidth)) {
            //用于补充留白的图片出现在屏幕
            for (int i = 0; i < mBitmapCount; i++) {
                mMatrix.reset();
                switch (mScrollOrientation) {
                    case OUT_SLIDE_TOP:
                        mMatrix.postTranslate(0f, (i + 1) * length + mPanDistance);
                        break;
                    case OUT_SLIDE_BOTTOM:
                        mMatrix.postTranslate(0f, measuredHeight - (i + 2) * length - mPanDistance);
                        break;
                    case OUT_SLIDE_LEFT:
                        mMatrix.postTranslate((i + 1) * length + mPanDistance, 0f);
                        break;
                    case OUT_SLIDE_RIGHT:
                        mMatrix.postTranslate(measuredWidth - (i + 2) * length - mPanDistance, 0f);
                        break;
                }
                canvas.drawBitmap(mSrcBitmap, mMatrix, mPaint);
            }
        }
        //绘制遮罩层
        if (mMaskLayerColor != Color.TRANSPARENT) {
            canvas.drawColor(mMaskLayerColor);
        }
        //延时重绘实现滚动效果
        if (mIsScroll) {
            getHandler().postDelayed(mRedrawRunnable, DEFAULT_DRAW_INTERVALS_TIME);
        }
    }

    /**
     * 重绘
     */
    private Runnable mRedrawRunnable = new Runnable() {
        @Override
        public void run() {
            int length = scrollOrientationIsVertical() ? mSrcBitmap.getHeight() : mSrcBitmap.getWidth();
            if (length + mPanDistance <= 0) {
                //第一张已完全滚出屏幕,重置平移距离
                mPanDistance = 0;
            }
            mPanDistance -= mIntervalIncreaseDistance;
            invalidate();
        }
    };

    /**
     * 开始滚动
     */
    public void startScroll() {
        if (mIsScroll) {
            return;
        }
        mIsScroll = true;
        getHandler().postDelayed(mRedrawRunnable, DEFAULT_DRAW_INTERVALS_TIME);
    }

    /**
     * 停止滚动
     */
    public void stopScroll() {
        if (!mIsScroll) {
            return;
        }
        mIsScroll = false;
        getHandler().removeCallbacks(mRedrawRunnable);
    }

    /**
     * 设置背景图 bitmap
     * 通过该方法设置的背景图,当 屏幕翻转/暗黑模式切换 等涉及到 activity 重构的情况出现时,需要在 activity 重构后重新设置背景图
     */
    public void setSrcBitmap(Bitmap srcBitmap) {
        boolean oldScrollStatus = mIsScroll;
        if (oldScrollStatus) {
            stopScroll();
        }
        Bitmap compressBitmap;
        if (srcBitmap.getConfig() != Bitmap.Config.RGB_565) {
            compressBitmap = srcBitmap.copy(Bitmap.Config.RGB_565, true);
        } else {
            compressBitmap = srcBitmap;
        }
        //按当前View宽度比例缩放 Bitmap
        mSrcBitmap = scaleBitmap(compressBitmap);
        //计算至少需要几个 bitmap 才能填满当前 view
        mBitmapCount = scrollOrientationIsVertical() ?
                getMeasuredHeight() / mSrcBitmap.getHeight() + 1
                :
                getMeasuredWidth() / mSrcBitmap.getWidth() + 1;
        if (!srcBitmap.isRecycled()) {
            srcBitmap.isRecycled();
            System.gc();
        }
        if (!compressBitmap.isRecycled()) {
            compressBitmap.isRecycled();
            System.gc();
        }
        if (oldScrollStatus) {
            startScroll();
        }
    }

    /**
     * 判断是否为竖直滚动
     */
    private boolean scrollOrientationIsVertical() {
        return mScrollOrientation == OUT_SLIDE_TOP || mScrollOrientation == OUT_SLIDE_BOTTOM;
    }

    /**
     * 设置滚动方向
     * @param scrollOrientation
     */
    public void setScrollOrientation(@ScrollOrientation int scrollOrientation) {
        mPanDistance = 0;
        mScrollOrientation = scrollOrientation;
        if (mSrcBitmap != null) {
            if (mDrawable != null && (mDrawable instanceof BitmapDrawable)) {
                Bitmap bitmap = ((BitmapDrawable) mDrawable).getBitmap();
                if (!bitmap.isRecycled()) {
                    setSrcBitmap(bitmap);
                    return;
                }
            }
            setSrcBitmap(mSrcBitmap);
        }
    }

    /**
     * 切换滚动方向
     */
    public void changeScrollOrientation() {
        mPanDistance = 0;
        if (mScrollOrientation == OUT_SLIDE_RIGHT) {
            mScrollOrientation = OUT_SLIDE_TOP;
        } else {
            mScrollOrientation++;
        }
        if (mSrcBitmap != null) {
            if (mDrawable != null && (mDrawable instanceof BitmapDrawable)) {
                Bitmap bitmap = ((BitmapDrawable) mDrawable).getBitmap();
                if (!bitmap.isRecycled()) {
                    setSrcBitmap(bitmap);
                    return;
                }
            }
            setSrcBitmap(mSrcBitmap);
        }
    }

    /**
     * 缩放Bitmap
     */
    private Bitmap scaleBitmap(Bitmap originBitmap) {
        int width = originBitmap.getWidth();
        int height = originBitmap.getHeight();
        int newHeight;
        int newWidth;
        if (scrollOrientationIsVertical()) {
            newWidth = getMeasuredWidth();
            newHeight = newWidth * height / width;
        } else {
            newHeight = getMeasuredHeight();
            newWidth = newHeight * width / height;
        }

        return Bitmap.createScaledBitmap(originBitmap, newWidth, newHeight, true);
    }
}

???4、布局文件src\main\res\layout\activity_main.xml

<?xml version="1.0" encoding="utf-8"?>
<com.giada.healthcode.SrcScrollFrameLayout xmlns:android="http://schemas.android.com/apk/res/android"
    xmlns:app="http://schemas.android.com/apk/res-auto"
    android:id="@+id/fl_main"
    android:layout_width="match_parent"
    android:layout_height="match_parent"
    app:maskLayerColor="#80000000"
    app:scrollOrientation="toTop"
    app:src="@drawable/picture">

    <Button
        android:id="@+id/btn_start"
        android:layout_width="220dp"
        android:layout_height="45dp"
        android:layout_gravity="center_horizontal|bottom"
        android:layout_marginBottom="220dp"
        android:text="开始滚动" />

    <Button
        android:id="@+id/btn_stop"
        android:layout_width="220dp"
        android:layout_height="45dp"
        android:layout_gravity="center_horizontal|bottom"
        android:layout_marginBottom="160dp"
        android:text="停止滚动" />

    <Button
        android:id="@+id/btn_change"
        android:layout_width="220dp"
        android:layout_height="45dp"
        android:layout_gravity="center_horizontal|bottom"
        android:layout_marginBottom="100dp"
        android:text="切换滚动方向" />
    <Button
        android:id="@+id/btn_speedup"
        android:layout_width="220dp"
        android:layout_height="45dp"
        android:layout_gravity="center_horizontal|bottom"
        android:layout_marginBottom="40dp"
        android:text="speed up" />
</com.giada.healthcode.SrcScrollFrameLayout>

???5、src\main\AndroidManifest.xml

<?xml version="1.0" encoding="utf-8"?>
<manifest xmlns:android="http://schemas.android.com/apk/res/android"
    package="com.giada.healthcode">

    <application
        android:allowBackup="true"
        android:icon="@mipmap/ic_launcher"
        android:label="@string/app_name"
        android:roundIcon="@mipmap/ic_launcher_round"
        android:supportsRtl="true"
        android:theme="@style/Theme.AppCompat.NoActionBar">
        <activity android:name=".MainActivity">
            <intent-filter>
                <action android:name="android.intent.action.MAIN" />

                <category android:name="android.intent.category.LAUNCHER" />
            </intent-filter>
        </activity>
    </application>

</manifest>

6、app\src\main\res\values\attrs.xml

<?xml version="1.0" encoding="utf-8"?>
<resources>
    <!--背景图片-->
    <attr name="src" format="reference" />
    <!--遮罩层颜色,建议带透明度-->
    <attr name="maskLayerColor" format="color" />
    <!--是否滚动-->
    <attr name="isScroll" format="boolean" />
    <!--滚动速度,建议取值区间 [1,50] -->
    <attr name="speed" format="integer">
        <enum name="slow" value="1" />
        <enum name="ordinary" value="3" />
        <enum name="fast" value="5" />
    </attr>
    <!-- 滚动方向,默认是竖直方向滚动-->
    <attr name="scrollOrientation" format="integer">
        <enum name="toTop" value="0" />
        <enum name="toBottom" value="1" />
        <enum name="toLeft" value="2" />
        <enum name="toRight" value="3" />
    </attr>
    <declare-styleable name="SrcScrollFrameLayout">
        <attr name="src" />
        <attr name="maskLayerColor" />
        <attr name="isScroll" />
        <attr name="speed" />
        <attr name="scrollOrientation" />
    </declare-styleable>
    <declare-styleable name="SrcLoopScrollFrameLayout">
        <attr name="src" />
        <attr name="maskLayerColor" />
        <attr name="isScroll" />
        <attr name="speed" />
        <attr name="scrollOrientation" />
    </declare-styleable>
</resources>

7、src\main\res\values\themes.xml

<resources xmlns:tools="http://schemas.android.com/tools">
    <!-- Base application theme. -->
    <style name="Theme.HealthCode" parent="Theme.MaterialComponents.DayNight.DarkActionBar">
        <!-- Primary brand color. -->
        <item name="colorPrimary">@color/purple_500</item>
        <item name="colorPrimaryVariant">@color/purple_700</item>
        <item name="colorOnPrimary">@color/white</item>
        <!-- Secondary brand color. -->
        <item name="colorSecondary">@color/teal_200</item>
        <item name="colorSecondaryVariant">@color/teal_700</item>
        <item name="colorOnSecondary">@color/black</item>
        <!-- Status bar color. -->
        <item name="android:statusBarColor" tools:targetApi="l">?attr/colorPrimaryVariant</item>
        <!-- Customize your theme here. -->
    </style>
</resources>

8、src\main\res\values\colors.xml

<?xml version="1.0" encoding="utf-8"?>
<resources>
    <color name="purple_200">#FFBB86FC</color>
    <color name="purple_500">#FF6200EE</color>
    <color name="purple_700">#FF3700B3</color>
    <color name="teal_200">#FF03DAC5</color>
    <color name="teal_700">#FF018786</color>
    <color name="black">#FF000000</color>
    <color name="white">#FFFFFFFF</color>
</resources>

三、有价值的参考文章

https://github.com/ziwenL/SrcScrollFrameLayout

Android 实现小红书登陆页面背景图无限滚动效果_wangqing830414的专栏-CSDN博客

Android 实现小红书登陆页面背景图无限滚动效果_weixin_38754349的博客-CSDN博客

Android 实现小红书登陆页面背景图无限滚动效果 - 程序员大本营

  移动开发 最新文章
Vue3装载axios和element-ui
android adb cmd
【xcode】Xcode常用快捷键与技巧
Android开发中的线程池使用
Java 和 Android 的 Base64
Android 测试文字编码格式
微信小程序支付
安卓权限记录
知乎之自动养号
【Android Jetpack】DataStore
上一篇文章      下一篇文章      查看所有文章
加:2022-01-29 23:11:58  更:2022-01-29 23:13:22 
 
开发: 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 12:21:21-

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