重铸安卓荣光——上传图片组件

痛点:

公司打算做安卓软件,最近在研究安卓,打算先绘制样式

研究发现安卓并不像前端有那么多组件库,甚至有些基础的组件都需要自己实现,记录一下自己实现的组件

成品展示

一个上传图片的组件

  1. 可以选择拍照或者从相册中上传

  2. 上传可以限制数量

  3. 上传后可以选择某张图片删除

动画

引入依赖

build.gradle中引入以下依赖

//图片选择器
implementation("com.github.wildma:PictureSelector:2.1.0")
//照片查看器,可以放大缩小照片
implementation("com.github.chrisbanes:PhotoView:2.3.0")
//自动换行的layout,帮助实现达到宽度后自动下一行
implementation("com.google.android:flexbox:2.0.1")

使用

使用只需要xml中添加该组件即可,其中app开头的属性都是为了让他可以自动换行

    <com.example.androidtest.test.UploadLayout
        android:id="@+id/uploadLayout"
        android:layout_width="match_parent"
        android:layout_height="wrap_content"
        app:flexWrap="wrap"
        app:alignItems="stretch"
        app:alignContent="stretch"
        app:justifyContent="flex_start"/>

初始化

创建UploadLayout继承FlexboxLayout

继承FlexboxLayout可以实现自动换行,当我们插入的图片占满一行后,会自己换行,如下所示

image-20240220160045978

加载添加按钮

初始化时需要把灰色的添加按钮加载进来

限制最大照片数

maxImage属性用来限制最大上传照片数量

public class UploadLayout extends FlexboxLayout {
    //最大上传图片数量
    private Integer maxImage = -1;
    
    private TextView uploadPhotoTextView;

    public UploadLayout(Context context) {
        super(context);
        init();
    }

    public UploadLayout(Context context, AttributeSet attrs) {
        super(context, attrs);
        TypedArray a = context.obtainStyledAttributes(attrs, R.styleable.UploadLayout);
        maxImage = a.getInteger(R.styleable.UploadLayout_maxImage, -1);
        a.recycle();
        init();
    }

    public UploadLayout(Context context, AttributeSet attrs, int defStyleAttr) {
        super(context, attrs, defStyleAttr);
        init();
    }
    
    private void init() {
        // 创建一个新的 TextView 控件
        uploadPhotoTextView = new TextView(getContext());

        // 设置控件的属性
        int weight = (int) UiUtils.dp2px(getContext(),80);
        uploadPhotoTextView.setId(View.generateViewId()); // 生成一个唯一的 ID
        uploadPhotoTextView.setWidth(weight); // 设置宽度为 80dp
        uploadPhotoTextView.setHeight(weight); // 设置高度为 80dp
        uploadPhotoTextView.setGravity(Gravity.CENTER); // 设置文本居中对齐
        uploadPhotoTextView.setBackground(ContextCompat.getDrawable(getContext(), R.color.viewfinder_text_color4)); // 设置背景颜色
        uploadPhotoTextView.setText("+"); // 设置文本内容为 "+"
        uploadPhotoTextView.setTextSize(30); // 设置文本大小为 30dp
        uploadPhotoTextView.setOnClickListener(new View.OnClickListener() {
            @Override
            public void onClick(View v) {
                // 在这里添加点击事件的逻辑
                uploadPhoto(v);
            }
        });

        // 设置控件的布局参数,可以根据需要设置边距等
        LinearLayout.LayoutParams layoutParams = new LinearLayout.LayoutParams(weight,weight);
        layoutParams.setMargins(0, 0, 10, 10); // 设置右边距为 10dp,底边距为 10dp
        uploadPhotoTextView.setLayoutParams(layoutParams);

        // 将 TextView 添加到父容器中
        addView(uploadPhotoTextView);
    }
}

创建控件时读取参数

在xml中直接使用时,走的是以下构造方法

    public UploadLayout(Context context, AttributeSet attrs) {
        super(context, attrs);
        TypedArray a = context.obtainStyledAttributes(attrs, R.styleable.UploadLayout);
        maxImage = a.getInteger(R.styleable.UploadLayout_maxImage, -1);
        a.recycle();
        init();
    }

限制最大上传数

    <com.example.androidtest.test.UploadLayout
        android:id="@+id/uploadLayout"
        android:layout_width="match_parent"
        android:layout_height="wrap_content"
        app:flexWrap="wrap"
        app:alignItems="stretch"
        app:alignContent="stretch"
        app:justifyContent="flex_start"
        app:maxImage="2"/>

需要在res/values/attrs.xml中添加以下代码,让他能读取到maxImage

    <declare-styleable name="UploadLayout">
        <attr name="maxImage" format="integer" />
    </declare-styleable>

添加点击事件

selectPicture设置false表示不需要裁剪,如果需要裁剪可以设置为true,测试时报错,好像原因是手机没有照片裁剪器

    public void uploadPhoto(View view){
        Activity activity = (Activity) view.getContext();
        //判断数量是否已经达到上线
        int imageViewCount = 0;
        for (int i = 0; i < getChildCount(); i++) {
            View childView = getChildAt(i);
            if (childView instanceof RelativeLayout) {
                imageViewCount++;
            }
        }
        if (imageViewCount == maxImage) {
            //达到上限
            Toast.makeText(getContext(), "图片上传已达上限", Toast.LENGTH_SHORT).show();
            return;
        }
        
        //打开照片选择器,
        PictureSelector
                .create(activity, PictureSelector.SELECT_REQUEST_CODE)
                .selectPicture(false);
    }

上传照片回调

我的做法是在uploadLayout中依次插入RelativeLayout

RelativeLayoutImageView用来显示图片,View用来显示删除框

点击查看大图,使用PhotoView可以拖拽放大缩小

public void handleActivityResult(int requestCode, int resultCode, Intent data) {
        if (requestCode == PictureSelector.SELECT_REQUEST_CODE) {
            if (data != null) {
                PictureBean pictureBean = data.getParcelableExtra(PictureSelector.PICTURE_RESULT);

                RelativeLayout relativeLayout = new RelativeLayout(getContext());
                ImageView imageView = new ImageView(getContext());
                int uploadIndex  = this.indexOfChild(uploadPhotoTextView);
                //设置大小
                int weight80 = (int) UiUtils.dp2px(getContext(),80);
                int weight20 = (int) UiUtils.dp2px(getContext(),20);
                // 设置布局参数
                FlexboxLayout.LayoutParams layoutParams = new FlexboxLayout.LayoutParams(weight80,weight80); 
                layoutParams.setMargins(0,0, 10,  10);
                layoutParams.setOrder(uploadIndex);
                relativeLayout.setLayoutParams(layoutParams);
                relativeLayout.addView(imageView);

                if (pictureBean.isCut()) {
                    imageView.setImageBitmap(BitmapFactory.decodeFile(pictureBean.getPath()));
                } else {
                    imageView.setImageURI(pictureBean.getUri());
                }

                //删除按钮
                View closeView = new View(getContext());
                closeView.setBackgroundResource(R.drawable.cross_shape);
                RelativeLayout.LayoutParams closeParams = new RelativeLayout.LayoutParams(weight20,weight20);

                // 设置ImageView在RelativeLayout中的位置,这里设置为右上角
                closeParams.addRule(RelativeLayout.ALIGN_PARENT_RIGHT);
                closeParams.addRule(RelativeLayout.ALIGN_PARENT_TOP);
                closeView.setLayoutParams(closeParams);
                relativeLayout.addView(closeView);

                //使用 Glide 加载图片
                Glide.with(this)
                        .load(pictureBean.isCut() ? pictureBean.getPath() : pictureBean.getUri())
                        .apply(RequestOptions.centerCropTransform()).into(imageView);

                //图片点击大图
                imageView.setOnClickListener(v -> {
                    // 创建一个 Dialog 来显示大图
                    Dialog dialog = new Dialog(getContext(), android.R.style.Theme_Black_NoTitleBar_Fullscreen);
                    dialog.setContentView(R.layout.dialog_image_preview);

                    PhotoView photoView = dialog.findViewById(R.id.photoView);

                    // 使用 Glide 加载大图到 PhotoView
                    Glide.with(this)
                            .load(pictureBean.isCut() ? pictureBean.getPath() : pictureBean.getUri()) // 替换为您的大图 URL
                            .into(photoView);

                    // 点击大图时关闭 Dialog
                    photoView.setOnClickListener(vm -> dialog.dismiss());

                    dialog.show();
                });

                //删除
                closeView.setOnClickListener(v -> {
                    androidx.appcompat.app.AlertDialog.Builder builder = new AlertDialog.Builder(v.getContext());
                    builder.setView(R.layout.dialog_delete_img);
                    builder.setCancelable(false);//能否被取消

                    AlertDialog dialog = builder.create();
                    dialog.show();
                    View cancel = dialog.findViewById(R.id.delete_img_cancel);
                    View commit = dialog.findViewById(R.id.delete_img_commit);
                    cancel.setOnClickListener(v1 -> dialog.dismiss());
                    commit.setOnClickListener(v1 -> {
                        RelativeLayout parentRelativeLayout= (RelativeLayout) closeView.getParent();
                        if (parentRelativeLayout != null) {
                            removeView(parentRelativeLayout);
                        }
                        dialog.dismiss();
                    });
                });

                addView(relativeLayout);
            }
        }
    }

删除弹窗:dialog_delete_img.xml

    <RelativeLayout
        android:layout_width="match_parent"
        android:layout_height="wrap_content"
        android:padding="15dp">
        <TextView
            android:id="@+id/title"
            android:layout_width="wrap_content"
            android:layout_height="wrap_content"
            android:text="确定要删除这张照片吗?"
            android:textColor="@color/black"
            android:textSize="15dp"
            android:layout_centerHorizontal="true"
            android:layout_marginBottom="30dp"
            />
        <RelativeLayout
            android:layout_width="match_parent"
            android:layout_height="wrap_content"
            android:layout_below="@+id/title"
            android:layout_marginHorizontal="30dp"
            >

            <Button
                android:id="@+id/delete_img_cancel"
                android:layout_width="120dp"
                android:layout_height="40dp"
                android:text="取消"
                android:textColor="@color/button_orange"
                android:textSize="15dp"
                android:layout_alignParentLeft="true"
                android:background="@drawable/tab_layout_item3"
                />

            <Button
                android:id="@+id/delete_img_commit"
                android:layout_width="120dp"
                android:layout_height="40dp"
                android:text="确定"
                android:textColor="@color/white"
                android:textSize="15dp"
                android:layout_alignParentRight="true"
                android:background="@drawable/tab_layout_item4"
                />
        </RelativeLayout>
    </RelativeLayout>

删除图案:cross_shape.xml

<?xml version="1.0" encoding="utf-8"?>
<!-- cross_shape.xml -->
<layer-list xmlns:android="http://schemas.android.com/apk/res/android">
    <!-- 背景色 -->
    <item>
        <shape android:shape="rectangle">
            <solid android:color="#80000000" />
        </shape>
    </item>

    <item>
        <rotate
            android:fromDegrees="45"
            android:toDegrees="45">
            <shape android:shape="line">
                <stroke android:width="1dp" android:color="#FFFFFF" />
            </shape>
        </rotate>
    </item>

    <item>
        <rotate
            android:fromDegrees="135"
            android:toDegrees="135">
            <shape android:shape="line">
                <stroke android:width="1dp" android:color="#FFFFFF" />
            </shape>
        </rotate>
    </item>

</layer-list>

重写Activity方法

在调用的页面的需要重写onActivityResult(),执行咱们的回调函数

public class MainActivity extends AppCompatActivity {
    private UploadLayout uploadLayout;

    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_main);
        uploadLayout = findViewById(R.id.uploadLayout);
    }

    @Override
    protected void onActivityResult(int requestCode, int resultCode, @Nullable Intent data) {
        super.onActivityResult(requestCode, resultCode, data);
        uploadLayout.handleActivityResult(requestCode,resultCode,data);
    }
}

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

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

相关文章

RSA之前端加密后端解密

RSA之前端加密后端解密 RSA加密解密方式有&#xff1a; &#xff08;1&#xff09;公钥加密&#xff0c;私钥解密&#xff1b; &#xff08;2&#xff09;私钥加密&#xff0c;公钥解密&#xff1b; 此文章中以下我使用的是前端公钥加密&#xff0c;后端私钥解密&#xff1b; …

提升竞争力!攻读在职硕士为职业发展加冕——社科院与杜兰大学金融管理硕士

在现如今竞争激烈的职场环境中&#xff0c;不断提升自身的竞争力是每个职场人士都面临的重要任务。攻读在职硕士学位成为越来越多人实现个人职业发展目标的首选方式之一。特别是社科院与杜兰大学合作开设的金融管理硕士项目&#xff0c;为那些希望在金融行业取得突破的职业人士…

欢迎来到IT时代----盘点曾经爆火全网的计算机电影

计算机专业必看的几部电影 计算机专业必看的几部电影&#xff0c;就像一场精彩的编程盛宴&#xff01;《黑客帝国》让你穿越虚拟世界&#xff0c;感受高科技的魅力&#xff1b;《社交网络》揭示了互联网巨头的创业之路&#xff0c;《源代码》带你穿越时间解救世界&#xff0c;这…

智慧驿站_智慧文旅驿站_轻松的驿站智慧公厕_5G智慧公厕驿站_5G模块化智慧公厕

多功能城市智慧驿站是在智慧城市建设背景下&#xff0c;所涌现的一种创新型社会配套设施。其中&#xff0c;智慧公厕作为城市智慧驿站的重要功能基础&#xff0c;具备社会配套不可缺少的特点&#xff0c;所以在应用场景上&#xff0c;拥有广泛的需求和要求。那么&#xff0c;城…

java-kotlin踩坑:错误:找不到符号(点击能跳转到对应类中)

问题描述&#xff1a; 在android用java调用一个kotlin定义的类时&#xff0c;导包正常&#xff0c;点击也能跳转到对应类中&#xff0c;但是在编译运行时会报错&#xff0c;提示找不到符号 解决方法&#xff1a; 第一步&#xff1a;在app级别的build.gradle中添加kotlin-and…

HarmonyOS4.0系统性深入开发35 弹性布局(Flex)

弹性布局&#xff08;Flex&#xff09; 概述 弹性布局&#xff08;Flex&#xff09;提供更加有效的方式对容器中的子元素进行排列、对齐和分配剩余空间。容器默认存在主轴与交叉轴&#xff0c;子元素默认沿主轴排列&#xff0c;子元素在主轴方向的尺寸称为主轴尺寸&#xff0…

【动态规划专栏】专题一:斐波那契数列模型--------4.解码方法

本专栏内容为&#xff1a;算法学习专栏&#xff0c;分为优选算法专栏&#xff0c;贪心算法专栏&#xff0c;动态规划专栏以及递归&#xff0c;搜索与回溯算法专栏四部分。 通过本专栏的深入学习&#xff0c;你可以了解并掌握算法。 &#x1f493;博主csdn个人主页&#xff1a;小…

blasterswap明牌空投

空投要点 明牌空投&#xff0c;blaster生态第一个swap&#xff0c;应该不会寒酸交互简单&#xff0c;仅需3步&#xff0c;零gas费仅仅要求加密钱包在eth链有过交易需要有x和discord账号 blasterswap空投简介 BlasterSwap 是Blast生态里面第一个SWAP项目&#xff0c;近期启动…

国开电大计算机科学与技术网络技术与应用试题及答案,分享几个实用搜题和学习工具 #媒体#其他#知识分享

这些软件以其强大的搜索引擎和智能化的算法&#xff0c;为广大大学生提供了便捷、高效的解题方式。下面&#xff0c;让我们一起来了解几款备受大学生欢迎的搜题软件吧&#xff01; 1.三羊搜题 这个是公众号 支持文字和语音查题!!! 学习通,知到,mooc等等平台的网课题目答案都…

OpenCV 4基础篇| 色彩空间类型转换

目录 1. 色彩空间基础2. 色彩空间类型2.1 GRAY 色彩空间2.2 BGR 色彩空间2.3 CMY(K) 色彩空间2.4 XYZ 色彩空间2.5 HSV 色彩空间2.6 HLS 色彩空间2.7 CIEL*a*b* 色彩空间2.8 CIEL*u*v* 色彩空间2.9 YCrCb 色彩空间 3. 类型转换函数3.1 cv2.cvtColor3.2 cv2.inRange 1. 色彩空间…

Fiddler与wireshark使用

Fiddler解决三个问题 1、SSL证书打勾&#xff0c;解析https请求 2、响应回来乱码&#xff0c;不是中文 3、想及时中止一下&#xff0c;查看实时的日志 4、搜索对应的关键字 问题1解决方案&#xff1a; 标签栏Tools下 找到https&#xff0c;全部打勾 Actions里面 第一个 t…

怎么在电脑上做工作笔记?电脑桌面电子笔记软件

在繁忙的职场中&#xff0c;随时随地记录工作笔记是许多职场人士的日常需求。这不仅包括了会议记录、项目进展&#xff0c;还有一些灵感、规划和工作要点&#xff0c;都需要随手记下&#xff0c;以便随时查看和回顾。那么我们如何在电脑上做工作笔记更高效、便捷呢&#xff1f;…

文献学习-1-Continuum Robots for Medical Interventions

Chapt 5. 连续体机构分析 5.1 文献学习 5.1.1 Continuum Robots for Medical Interventions Authors: PIERRE E. DUPONT , Fellow IEEE, NABIL SIMAAN , Fellow IEEE, HOWIE CHOSET , Fellow IEEE, AND CALEB RUCKER , Member IEEE 连续体机器人在医学上得到了广泛的应用&a…

爱校对软件——清华大学研发的全能文字处理助手

随着数字化和信息化的深入发展&#xff0c;高效、准确的文字处理工具成为了各行各业的迫切需求。清华大学人机交互实验室推出的“爱校对”软件&#xff0c;作为一款先进的文字处理工具&#xff0c;正逐渐成为专业编辑、写作者、学生、法律从业者、政府工作人员、商业从业者、出…

计算机视觉学习指南(划分为20个大类)

计算机视觉的知识领域广泛而庞杂&#xff0c;涵盖了众多重要的方向和技术。为了更好地组织这些知识&#xff0c;我们需要遵循无交叉无重复&#xff08;Mutually Exclusive Collectively Exhaustive&#xff0c;MECE&#xff09;的原则&#xff0c;并采用循序渐进的方式进行分类…

开发一款招聘小程序需要具备哪些功能?

随着时代的发展&#xff0c;找工作的方式也在不断变得简单&#xff0c;去劳务市场、人才市场的方式早就已经过时了&#xff0c;现在大多数年轻人都是直接通过手机来找工作。图片 找工作类的平台不但能扩大企业的招聘渠道&#xff0c;还能节省招聘的成本&#xff0c;方便求职者进…

Solidworks:钣金的折弯系数、K因子、折弯扣除

在SolidWorks的钣金件设计中&#xff0c;“折弯系数”、“K因子”和“折弯扣除”都是与折弯加工相关的重要参数。 “折弯系数”是一个用于描述金属材料在折弯过程中应力分布非均匀性的指标。它反映了金属板材在弯曲时内外表面应力的分布情况&#xff0c;是判断材料是否适合进行…

音乐与步伐同行:南卡、韶音和墨觉的骨传导耳机深度评测

在快节奏的现代生活中&#xff0c;音乐成为了许多人精神慰藉的方式之一。特别是对于那些热爱运动的人来说&#xff0c;音乐不仅是他们运动过程中的最佳伴侣&#xff0c;更是激发潜力&#xff0c;突破极限的源动力。但是在运动的过程中如何享受到最佳的音乐体验呢&#xff1f;这…

DAY20 结构和其他数据形式(上)【一万六千字超详细】

文章目录 前言14.1 示例问题&#xff1a;创建图书目录14.2建立结构声明14.3定义结构变量14.3.1 初始化结构14.3.2 访问结构成员14.3.2 结构的初始化器 14.4 结构数组14.4.1 声明结构数组14.4.2 标识结构数组的成员 14.3 嵌套结构14.6 指向结构的指针14.6.1 声明和初始化结构指针…

技术选型指南:Oracle、SQL Server还是DB2?

Oracle vs SQL Server vs DB2 - 选哪个好&#xff1f; 在企业级数据管理领域&#xff0c;常用的几个选择有Oracle、SQL Server和DB2。 首先&#xff0c;我们从以下几个方面做一下对比&#xff1a; 1. 性能和稳定性&#xff1a; Oracle: Oracle就像是那种精密的瑞士手表&…