图片模块封装:Glide高级使用+使用设计模式图片框架封装+Bitmap尺寸压缩和质量压缩+Bitmap加载大图长图

图片模块封装:Glide高级使用+使用设计模式图片封装+Bitmap尺寸压缩和质量压缩+Bitmap加载大图长图

  • 一.如何更换图片框架
  • 二.策略模式+构建者模式图片框架搭建
    • 1.ImageOptions图片参数设置
    • 2.IImageLoader接口以及实现子类
    • 3.图片加载策略
    • 4.ImageLoaderManager
    • 6.业务模块中使用:
  • 三.Glide配置
    • 1.依赖:
    • 2.缓存配置:
    • 3.网络配置:glide默认使用httpUrlConnection完成网络请求,可以改成okhttp
  • 四.Bitmap三级缓存二次采样
  • 五.长图大图处理

一.如何更换图片框架

框架设计过程中对于对于架构要求高内聚低耦合,图片加载框架中引
入三方框架提示开发效率,对于技术选型后的方案可能后面需求的变更原三方
sdk无法满足当前业务需求,故而需要更换原有sdk,为了将更改降到最低,所
有前面设计图片加载框架时要考虑当前这个风险点

使用设计模式来进一步解耦代码耦合度,来解决隔离风险点的目的,
即定义接口层业务依赖抽象即当前接口,具体实现有不同三方sdk完成。
因为业务依赖的是接口层对应后期代码维护更改量会控制在最小,对于原软件
稳定性影响也会极小达到更换图片加载框架的目的。

二.策略模式+构建者模式图片框架搭建

1.ImageOptions图片参数设置

/**
 * @Author : yaotianxue
 * @Time : On 2023/5/22 15:04
 * @Description : ImageOptions图片设置
 */
class ImageOptions( var placeImage:Int,//占位符
                    var errorImage:Int,//错误图
                    var isStaticImage:Boolean,//是否为静态图片
                    var isGif:Boolean,//是否为动图
                    var imageSize:Array<Int>,//图片大小
                    var skipMemoryCache:Boolean,//关闭内存缓存
                    var skipDiskCache:Boolean//关闭磁盘缓存
){
    constructor(builder: Builder):this(
        builder.placeImage,
        builder.errorImage,
        builder.isStaticImage,
        builder.isGif,
        builder.imageSize,
        builder.skipMemoryCache,
        builder.skipDiskCache
    )

    class Builder{
         var placeImage:Int = 0//占位符
         var errorImage:Int = 0 //错误图
         var isStaticImage:Boolean = true//是否为静态图片
         var isGif:Boolean = false//是否为动图
         var imageSize:Array<Int> = arrayOf(0,0)//图片大小
         var skipMemoryCache:Boolean = false//关闭内存缓存
         var skipDiskCache:Boolean = false//关闭磁盘缓存
        fun  setPlaceImage(placeImage:Int):Builder{
            this.placeImage = placeImage
            return this
        }
        fun  setErrorImage(errorImage:Int):Builder{
            this.errorImage = errorImage
            return this
        }
        fun build():ImageOptions{
            return ImageOptions(this)
        }
    }
   
}

2.IImageLoader接口以及实现子类

/**
 * @Author : yaotianxue
 * @Time : On 2023/5/22 15:03
 * @Description : IImageLoader
 */
interface IImageLoader {
    /**
     * 加载本地图片到指定图片控件
     */
    fun loadFileIntoImageView(context: Context,file: File,target: ImageView,config: ImageOptions)
    /**
     * 加载网络图片到指定图片控件
     */
    fun loadUrlIntoImageView(context: Context,url: String,target: ImageView,config: ImageOptions)
    /**
     * 加载资源图片到指定图片控件
     */
    fun loadResourceIntoImageView(context: Context,source: Int,target: ImageView,config: ImageOptions)
    /**
     * 加载Uri图片到指定图片控件
     */
    fun loadUriIntoImageView(context: Context, uri: Uri, target: ImageView, config: ImageOptions)
    /**
     * 加载二进制数组到指定图片控件
     */
    fun loadByteArrayIntoImageView(context: Context,bytes: Array<Byte>,target: ImageView,config: ImageOptions)
}

GlideImageLoader/FrscoImageLoader/PicassoImageLoader

/**
 * @Author : yaotianxue
 * @Time : On 2023/5/22 16:50
 * @Description : GlideImageLoader
 */
class GlideImageLoader:IImageLoader {
    override fun loadFileIntoImageView(
        context: Context,
        file: File,
        target: ImageView,
        config: ImageOptions,
    ) {
        loadImageView(context,file,target,config)
    }

    override fun loadUrlIntoImageView(
        context: Context,
        url: String,
        target: ImageView,
        config: ImageOptions,
    ) {
        loadImageView(context,url,target,config)
    }

    override fun loadResourceIntoImageView(
        context: Context,
        source: Int,
        target: ImageView,
        config: ImageOptions,
    ) {
        loadImageView(context,source,target,config)
    }

    override fun loadUriIntoImageView(
        context: Context,
        uri: Uri,
        target: ImageView,
        config: ImageOptions,
    ) {
        loadImageView(context,uri,target,config)
    }

    override fun loadByteArrayIntoImageView(
        context: Context,
        bytes: Array<Byte>,
        target: ImageView,
        config: ImageOptions,
    ) {
        loadImageView(context,bytes,target,config)
    }


    /**
     * 加载
     */
    private fun loadImageView(
        context: Context,
        source: Any,
        target: ImageView,
        config: ImageOptions,
    ) {
    //内存泄漏:该回收的资源无法被回收掉 context是activity,当页面销毁该回收activity,但是由于Glide引用当前activity、 导致activity无法回收
        //解决方案1:使用弱应用
        //解决方案2:activity传入上下文不要传this,传入application
        var weakReference = WeakReference<Context>(context)//弱引用
        if(weakReference.get() == null){
            return
        }
        var builder = GlideApp.with(context).load(source)
        setBuilderOptions(builder,config)
        builder.into(target)

    }

    /**
     * 设置图片参数
     */
    private fun setBuilderOptions(builder: GlideRequest<Drawable>, config: ImageOptions) {
        config.let {
            var options = RequestOptions()
            if(config.placeImage != 0){
                options.placeholder(config.placeImage)
            }
            if(config.errorImage != 0){
                options.error(config.errorImage)
            }
            config.imageSize.let {
                if(config.imageSize.size != 2){
                    throw IllegalArgumentException("please set imageSize length size is 2")
                }
                options.override(config.imageSize[0],config.imageSize[1])
            }
            if(config.skipDiskCache)  options.diskCacheStrategy(DiskCacheStrategy.NONE)
            if(config.skipMemoryCache) options.skipMemoryCache(true)
            builder.apply(options)
        }
    }
}

3.图片加载策略

/**
 * @Author : yaotianxue
 * @Time : On 2023/5/22 18:51
 * @Description : ImageStrategy
 */
enum class ImageStrategy {
    Glide,Picasso,Fresco
}

4.ImageLoaderManager

/**
 * @Author : yaotianxue
 * @Time : On 2023/5/23 15:56
 * @Description : ImageLoaderManager
 */
class ImageLoaderManager(var imageStrategy: ImageStrategy) {
    //策略模式创建不同的ImageLoader
    private var imageLoader:IImageLoader = when(imageStrategy){
        ImageStrategy.Glide -> GlideImageLoader()
        ImageStrategy.Fresco -> FrescoImageLoader()
        ImageStrategy.Picasso -> PicassoImageLoader()
    }

    /**
     * 加载不同的资源类型
     */
    fun loadIntoImageView(context: Context, source:Any, target: ImageView, config: ImageOptions){
        //is 判断source数据类型     as 是强转
        when(source){
            is String -> imageLoader.loadUrlIntoImageView(context,source,target,config)
            is File -> imageLoader.loadFileIntoImageView(context,source,target,config)
            is Uri -> imageLoader.loadUriIntoImageView(context,source,target,config)
            is Int -> imageLoader.loadResourceIntoImageView(context,source,target,config)
            is Array<*> -> imageLoader.loadByteArrayIntoImageView(context,
                source as Array<Byte>,target,config)
        }
    }
    
}

6.业务模块中使用:

/**
 * @Author : yaotianxue
 * @Time : On 2023/5/23 16:03
 * @Description : ImageUtils
 */
class ImageUtils {
    companion object{
        val manager = ImageLoaderManager(ImageStrategy.Glide)
        val options: ImageOptions = ImageOptions.Builder()
            .setPlaceImage(R.mipmap.ic_launcher)
            .setErrorImage(R.mipmap.ic_launcher)
            .setSkipDiskCache(false)
            .setSkipMemoryCache(true)
            .build()

        fun  loadImageView(context: Context,source:Any,target:ImageView){
            manager.loadIntoImageView(context,source,target,options)
        }

    }
}
ImageUtils.loadIageView(this,"",iv)

三.Glide配置

1.依赖:

config.gradle配置:

 //Glide
    // Glide集成OkHttp时需要使用的库,库已经将需要适配Okhhtp的大部分代码封装,注意如果之前已经使用了okhttp依赖注释掉
    libIntegration =  'com.github.bumptech.glide:okhttp3-integration:4.13.0'
    libGlide = 'com.github.bumptech.glide:glide:4.13.0'
    libGlideCompiler = 'com.github.bumptech.glide:compiler:4.13.0'//Glide注解处理器的依赖

library-base封装网络

   //glide图片框架
    api libGlide
    api libIntegration
    kapt libGlideCompiler

项目结构:
在这里插入图片描述

2.缓存配置:

/**
 * @Author : yaotianxue
 * @Time : On 2023/5/22 17:03
 * @Description : MyGlideModule 配置glide缓存
 */
@GlideModule
class CacheGlideModule:AppGlideModule() {
    override fun applyOptions(context: Context, builder: GlideBuilder) {
        //super.applyOptions(context, builder)
        //设置内存缓存大小:根据机器自动计算
//        var  memorySizeCalculator = MemorySizeCalculator.Builder(context).build()
//        builder.setMemoryCache(LruResourceCache(memorySizeCalculator.memoryCacheSize.toLong()))
        //设置内存缓存大小:10M
        builder.setMemoryCache(LruResourceCache(10*1024*1024))
        //设置磁盘缓存大小:500M 默认250M 设置磁盘缓存文件夹名称 "my_image" 默认 "image_manager_disk_cache"
        builder.setDiskCache(InternalCacheDiskCacheFactory(context,"my_image",500*1024*1024))//修改磁盘缓存的文件夹和磁盘大小
    }
}

3.网络配置:glide默认使用httpUrlConnection完成网络请求,可以改成okhttp

/**
 * @Author : yaotianxue
 * @Time : On 2023/5/22 17:07
 * @Description : OkhttpGlideModule:配置okhttp认证所有证书,可以认证自定义ca证书
 */
@GlideModule
class OkhttpGlideModule:LibraryGlideModule() {
    override fun registerComponents(context: Context, glide: Glide, registry: Registry) {
        //        super.registerComponents(context, glide, registry)
        var client = OkHttpClient.Builder()
            .sslSocketFactory(sSLSocketFactory,trustManager)
            .build()
        registry.replace(GlideUrl::class.java, InputStream::class.java, OkHttpUrlLoader.Factory(client))
    }

    /** 获取一个SSLSocketFactory */
    val sSLSocketFactory: SSLSocketFactory
        get() = try {
            val sslContext = SSLContext.getInstance("SSL")
            sslContext.init(null, arrayOf(trustManager), SecureRandom())
            sslContext.socketFactory
        } catch (e: Exception) {
            throw RuntimeException(e)
        }

    /** 获取一个忽略证书的X509TrustManager */
    val trustManager: X509TrustManager
        get() = object : X509TrustManager {
            override fun checkClientTrusted(chain: Array<X509Certificate>, authType: String) { }
            override fun checkServerTrusted(chain: Array<X509Certificate>, authType: String) { }
            override fun getAcceptedIssuers(): Array<X509Certificate> { return arrayOf() }
        }
}

四.Bitmap三级缓存二次采样

传送门走你!!

五.长图大图处理

https://www.jianshu.com/p/5ec13b295dd0


import android.app.appsearch.GetByDocumentIdRequest;
import android.content.Context;
import android.graphics.Bitmap;
import android.graphics.BitmapFactory;
import android.graphics.BitmapRegionDecoder;
import android.graphics.BitmapShader;
import android.graphics.Canvas;
import android.graphics.Rect;
import android.util.AttributeSet;
import android.util.Log;
import android.view.GestureDetector;
import android.view.InputDevice;
import android.view.MotionEvent;
import android.view.View;
import android.view.ViewConfiguration;

import java.io.FileDescriptor;
import java.io.IOException;
import java.io.InputStream;

/**
 * @Author : yaotianxue
 * @Time : On 2023/5/22 19:47
 * @Description : LargeImageView
 */
public class LargeImageView extends View implements GestureDetector.OnGestureListener {
    private BitmapRegionDecoder mDecoder;
    //绘制的区域
    private volatile Rect mRect = new Rect();
    private int mScaledTouchSlop;
    //分别记录上次的滑动的坐标
    private int mLastX = 0;
    private int mLastY = 0;
    //图片的宽度和高度
    private int mImageWidth,mImageHeight;
    //手势控制器
    private GestureDetector mGestureDetector;
    //Bitmap工厂参数配置
    private BitmapFactory.Options mOptions;

    private String name;

    public void setName(String name) {
        this.name = name;
    }

    public LargeImageView(Context context) {
        super(context);
        init(context,null);
    }

    public LargeImageView(Context context, AttributeSet attrs) {
        super(context, attrs);
        init(context,attrs);
    }



    public LargeImageView(Context context, AttributeSet attrs, int defStyleAttr) {
        super(context, attrs, defStyleAttr);
        init(context,attrs);
    }

    public LargeImageView(Context context, AttributeSet attrs, int defStyleAttr, int defStyleRes) {
        super(context, attrs, defStyleAttr, defStyleRes);
        init(context,attrs);
    }

    private void init(Context context, AttributeSet attrs) {
        //设置图片参数,如果对图片要求高采用ARGB_8888
        mOptions = new BitmapFactory.Options();
        mOptions.inPreferredConfig = Bitmap.Config.RGB_565;

        mScaledTouchSlop = ViewConfiguration.get(getContext()).getScaledTouchSlop();
        Log.d("ytx", "init: "+mScaledTouchSlop);
        //初始化手势控制器
        mGestureDetector = new GestureDetector(context,this);
        InputStream inputStream = null;
        try {
            inputStream = context.getResources().getAssets().open("demo.jpg");
            //初始化BitmapRegionDecoder,并用他显示图片
            mDecoder = BitmapRegionDecoder.newInstance(inputStream,false);
            //设置为true只采取图片的宽度和高度,不加载进内存
            BitmapFactory.Options options = new BitmapFactory.Options();
            options.inJustDecodeBounds = true;
            BitmapFactory.decodeStream(inputStream,null,options);
            mImageHeight = options.outHeight;
            mImageWidth = options.outWidth;

        } catch (IOException e) {
            e.printStackTrace();
        }finally {
            if(inputStream != null){
                try {
                    inputStream.close();
                } catch (IOException e) {
                    e.printStackTrace();
                }
            }
        }

    }
    //把触摸事件交给手势控制器处理
    @Override
    public boolean onTouchEvent(MotionEvent event) {
        return mGestureDetector.onTouchEvent(event);
    }

    @Override
    public boolean onDown(MotionEvent e) {
        mLastX = (int) e.getRawX();
        mLastY = (int) e.getRawY();
        return true;
    }

    @Override
    public void onShowPress(MotionEvent e) {

    }

    @Override
    public boolean onSingleTapUp(MotionEvent e) {
        return false;
    }

    @Override
    public boolean onScroll(MotionEvent e1, MotionEvent e2, float distanceX, float distanceY) {
        int x = (int) e2.getRawX();
        int y = (int) e2.getY();
        move(x,y);
        return true;
    }
    //移动的时候更新图片的显示的区域
    private void move(int x, int y) {
        int deltaX = x - mLastX;
        int deltaY = y - mLastY;
        if(mImageWidth > getWidth()){
            mRect.offset(-deltaX,0);
            if(mRect.right < mImageWidth){
                mRect.right = mImageWidth;
                mRect.left = mImageWidth - getWidth();
            }
            if(mRect.left < 0){
                mRect.left = 0;
                mRect.right = getRight();
            }
            invalidate();
        }

        if(mImageHeight > getHeight()){
            mRect.offset(0,-deltaY);
            if(mRect.bottom > mImageHeight){
                mRect.bottom = mImageHeight;
                mRect.top = mImageHeight - getHeight();
            }
            if(mRect.top < 0){
                mRect.top = 0;
                mRect.bottom = getHeight();
            }
            invalidate();
        }
        mLastX = x;
        mLastY = y;

    }

    @Override
    public void onLongPress(MotionEvent e) {
        mLastX = (int) e.getRawX();
        mLastY = (int) e.getRawY();
    }

    @Override
    public boolean onFling(MotionEvent e1, MotionEvent e2, float velocityX, float velocityY) {
        int x = (int)e2.getRawX();
        int y = (int) e2.getRawY();
        move(x,y);
        return true;
    }

    @Override
    protected void onDraw(Canvas canvas) {
        Bitmap bitmap = mDecoder.decodeRegion(mRect,mOptions);
        canvas.drawBitmap(bitmap,0,0,null);
    }

    @Override
    protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
        super.onMeasure(widthMeasureSpec, heightMeasureSpec);
        int width = getMeasuredWidth();
        int height = getMeasuredHeight();
        int imageWidth = mImageWidth;
        int imageHeight = mImageHeight;
        mRect.left = imageWidth/2 - width/2;
        mRect.top = imageHeight/2 - height/2;
        mRect.right = mRect.left +width;
        mRect.bottom = mRect.top + height;

    }
}
  

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:/a/22193.html

如若内容造成侵权/违法违规/事实不符,请联系我们进行投诉反馈qq邮箱809451989@qq.com,一经查实,立即删除!

相关文章

tcp/ip

这里写自定义目录标题 线程 防止阻塞 123 windows下4 https://zhuanlan.zhihu.com/p/139454200 https://www.bilibili.com/video/BV1eg411G7pW/?spm_id_from333.337.search-card.all.click&vd_sourcee7d12c9f66ab8294c87125a95510dac9 with socket.socket() as s:s.bind(…

小航编程题库2022年NOC决赛图形化(小高组)(含题库教师学生账号)

需要在线模拟训练的题库账号请点击 小航助学编程在线模拟试卷系统&#xff08;含题库答题软件账号&#xff09;_程序猿下山的博客-CSDN博客 单选题3.0分 删除编辑 答案:A 第1题运行下面的程序&#xff0c;最终“我的变量”的值是多少&#xff1f; A、5B、10C、25D、30 答案…

计及N-k安全约束的含光热电站电力系统优化调度模型【IEEE14节点、118节点】(Matlab代码实现)

&#x1f4a5;&#x1f4a5;&#x1f49e;&#x1f49e;欢迎来到本博客❤️❤️&#x1f4a5;&#x1f4a5; &#x1f3c6;博主优势&#xff1a;&#x1f31e;&#x1f31e;&#x1f31e;博客内容尽量做到思维缜密&#xff0c;逻辑清晰&#xff0c;为了方便读者。 ⛳️座右铭&a…

南京邮电大学算法与设计实验三:动态规划法(最全最新,与题目要求一致)

实验原理&#xff1a; 1、用动态规划法和备忘录方法实现求两序列的最长公共子序列问题。要求掌握动态规划法思想在实际中的应用&#xff0c;分析最长公共子序列的问题特征&#xff0c;选择算法策略并设计具体算法&#xff0c;编程实现两输入序列的比较&#xff0c;并输出它们的…

编译原理之词法分析实验(附完整C/C++代码与总结)

一、实验内容 通过完成词法分析程序&#xff0c;了解词法分析的过程。编制一个读单词程序&#xff0c;对PL/0语言进行词法分析&#xff0c;把输入的字符串形式的源程序分割成一个个单词符号&#xff0c;即基本保留字、标识符、常数、运算符、分界符五大类。 对PL/0语言进行词法…

【野火启明_瑞萨RA6M5】按键输入检测

文章目录 一、GPIO输入——按键输入检测二、硬件设计三、软件设计下载验证 一、GPIO输入——按键输入检测 按键检测原理 按键机械触点断开、闭合时&#xff0c;由于触点的弹性作用&#xff0c;按键开关不会马上稳定接通或一下子断开&#xff0c;使用按键时会产生 下图中的带波…

APlayer MetingJS 音乐播放器使用指南

文章目录 1.引用2.安装3.APlayer 原生用法4.MetingJS 的用法 1.引用 APlayer 是一个简洁漂亮、功能强大的 Html5 音乐播放器&#xff0c;GitHub地址&#xff1a;https://github.com/DIYgod/APlayer MetingJS 是为 APlayer 添加网易云、QQ音乐等支持的插件&#xff0c;GitHub地…

MySQL 用户管理

目录 用户管理 用户 用户信息 创建用户 删除用户 修改用户密码 数据库的权限 给用户 注意&#xff1a;如果发现赋权限后&#xff0c;没有生效&#xff0c;执行如下指令&#xff1a; 回收权限 用户管理 如果我们只能使用 root 用户&#xff0c;这样存在安全隐患。这时…

用streamlit,几行代码就可以拥有漂亮图表!

大家注意&#xff1a;因为微信最近又改了推送机制&#xff0c;经常有小伙伴说错过了之前被删的文章&#xff0c;比如前阵子冒着风险写的爬虫&#xff0c;再比如一些限时福利&#xff0c;错过了就是错过了。 所以建议大家加个星标&#xff0c;就能第一时间收到推送。&#x1f44…

QTableWidget样式设置

QTableWidget的样式分为几个部分&#xff1a; 分别是&#xff1a; 外框&#xff1a;QTableWidget 表头&#xff1a;QHeaderView 表头字段&#xff1a;QHeaderView::section 表格&#xff1a;QTableWidget::item 选中的表格&#xff1a;QTableWidget::item::selected 水平滚动条…

JDBC详解(六):数据库事务(超详解)

JDBC详解&#xff08;六&#xff09;&#xff1a;数据库事务&#xff08;超详解&#xff09; 前言一、数据库事务介绍二、JDBC事务处理三、事务的ACID属性1、数据库的并发问题2、四种隔离级别3、在MySql中设置隔离级别 前言 本博主将用CSDN记录软件开发求学之路上亲身所得与所…

海康威视 2024届 数字逻辑设计 实习笔试分析

说明 记录一下 5月11日晚&#xff0c;做的海康威视的一场笔试。分享给需要的IC人。 岗位&#xff1a;数字逻辑设计工程师&#xff08;浙江 杭州&#xff09; 转载需要本人同意&#xff01; 我的见解不一定都是准确的&#xff0c;欢迎评论区交流指正~~ 单选题 1、&#xff…

一分钟带你了解网络安全(如何自学)

一、关于网络安全职业 早些年&#xff0c;网络安全刚起步&#xff0c;作为一个网络安全从业人员&#xff0c;最苦恼的事情就是每当回到村里变成狗蛋儿的时候&#xff0c;七大姑八大姨&#xff0c;邻里乡亲&#xff0c;村子里的各种人都会来找你&#xff0c;狗蛋儿&#xff0c;你…

研报精选230519

目录 【行业230519头豹研究院】2023年中国产后康复设备行业词条报告 【行业230519山西证券】有色金属行业周报&#xff1a;锂价快速回升&#xff0c;释放锂电行业复苏信号 【行业230519头豹研究院】2023年中国氢能重卡行业词条报告 【个股230519西南证券_森麒麟】腾飞的高端轮胎…

漏扫工具-xray 1.9.10(文末附下载)

一、工具介绍 一款功能强大的安全评估工具 二、使用说明 1.使用基础爬虫爬取并对爬虫爬取的链接进行漏洞扫描 xray webscan --basic-crawler http://example.com --html-output vuln.html 2.使用 HTTP 代理进行被动扫描 xray webscan --listen 127.0.0.1:7777 --html-outp…

【sentinel】Sentinel工作主流程以流控规则源码分析

Sentinel工作主流程 在Sentinel里面&#xff0c;所有的资源都对应一个资源名称&#xff08;resourceName&#xff09;&#xff0c;每次资源调用都会创建一个Entry对象。Entry可以通过对主流框架的适配自动创建&#xff0c;也可以通过注解的方式或调用SphU API显式创建。Entry创…

前端026_菜单模块_新增功能

菜单模块_新增功能 1、需求分析2、新增组件实现3、列表引用新增组件4、关闭弹出窗口5、校验表单数据6、提交表单数据6.1、Mock 添加新增模拟接口6.2、Api 调用接口6.3、测试新增功能1、需求分析 菜单管理中有两处有 新增 按钮: 条件区域的是新增一级菜单,传递的参数是0。列表…

Compose 二三事:绘制原理

setContent做了什么 我们基于一个最简单的例子进行分析 class MainActivity : ComponentActivity() {override fun onCreate(savedInstanceState: Bundle?) {super.onCreate(savedInstanceState)setContent {Text(text "Hello World!")}} }这里setContent做了什么…

NetApp FAS 混合闪存阵列协助您建构简单易用、聪明智慧、值得信赖的储存基础架构

NetApp FAS 混合闪存阵列 主要优势 1、简单易用&#xff1a;节省您宝贵的时间、金钱和人力 •几分钟内完成储存资源配置。 •以获证实的效率技术降低成本。 •可在单一系统上管理档案与区块资料。 2、聪明智慧&#xff1a;灵活因应瞬息万变的业务需求 •以不中断营运的方式扩…

java(springboot+ssm)/python/php/nodejs/基于vue的景区门票预约管理系统

后端&#xff1a;java(springbootssm)/python/php/nodejs/ 开发运行&#xff1a;微信开发者/hbuilderx 后端:idea/eclipse/vscode/pycharm 模块划分&#xff1a;公告类型、公告信息、用户信息、用户咨询、地区信息、景区信息、景区开放、景区预约、统计信息 本技术是Java平台的…