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

news2024/10/7 20:29:07

痛点:

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

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

成品展示

一个上传图片的组件

  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);
    }
}

补充

昨天看了一下,每次使用的时候都要手动指定自动换行(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"/>

修改init方法
init方法中添加这几行,提前设置属性即可

        super.setFlexWrap(FlexWrap.WRAP);
        super.setAlignItems(AlignItems.STRETCH);
        super.setAlignContent(AlignContent.STRETCH);
        super.setJustifyContent(JustifyContent.FLEX_START);

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

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈,一经查实,立即删除!

相关文章

php反序列化原理常见的魔术方法

序列化是什么&#xff1f; 要想了解反序列化&#xff0c;就先要知道序列化是什么。下面是是一串序列化数组&#xff1a; a:2:{s:4:"name";s:6:"cike_y";s:3:"age";i:18;}a表示array&#xff08;数组&#xff09;&#xff0c;2表示这个数组有两…

计算机网络Day1--计算机网络体系

1.三网合一 电信网络、广播电视网络、计算机网络&#xff08;最基础最重要发展最快&#xff09; 2.Internet 名为国际互联网、因特网&#xff0c;指当前全球最大的、开放的、由众多网络相互连接而成的特定互连网&#xff0c;采用TCP/IP 协议族作为通信的规则&#xff0c;前…

NoSQL 数据库管理工具,搭载强大支持:Redis、Memcached、SSDB、LevelDB、RocksDB,为您的数据存储提供无与伦比的灵活性与性能!

NoSQL 数据库管理工具&#xff0c;搭载强大支持&#xff1a;Redis、Memcached、SSDB、LevelDB、RocksDB&#xff0c;为您的数据存储提供无与伦比的灵活性与性能&#xff01; 【官网地址】&#xff1a;http://www.redisant.cn/nosql 介绍 直观的用户界面 从单一应用程序中同…

设计模式----开题

简介&#xff1a; 本文主要介绍设计模式中的六大设计原则。开闭原则&#xff0c;里氏代换原则&#xff0c;依赖倒转原则&#xff0c;接口隔离原则&#xff0c;迪米特原则和合成复用原则。这几大原则是设计模式使用的基础&#xff0c;在使用设计模式时&#xff0c;应该牢记这六大…

IDEA 如何运行SpringBoot项目(手把手超详细截图)

IDEA 如何运行SpringBoot项目&#xff08;手把手超详细截图&#xff09; 第一章:github 导入项目 在GitHub上面找到我们需要部署项目的URL&#xff0c;并且复制粘贴到IDEA中&#xff0c;如下图所示。 第二章&#xff1a;Maven的准备与部署 由于idea自带Maven环境&#xff0c…

利用vite快速搭建vue3项目

1、选择一个文件夹&#xff0c;在vscode终端打开&#xff0c;输入命令【npm create vitelatest】 npm create vitelatest 2、提示你输入项目名称之后&#xff0c;我这里设置的是【rookiedemo】 3、回车之后&#xff0c;出现选择框架的提示&#xff0c;我们选择【vue】回车 4、…

Python 数据可视化之密度散点图 Density Scatter Plot

&#x1f349; CSDN 叶庭云&#xff1a;https://yetingyun.blog.csdn.net/ 密度散点图&#xff08;Density Scatter Plot&#xff09;&#xff0c;也称为密度点图或核密度估计散点图&#xff0c;是一种数据可视化技术&#xff0c;主要用于展示大量数据点在二维平面上的分布情况…

ElementUI table表格组件实现双击编辑单元格失去焦点还原,支持多单元格

在使用ElementUI table表格组件时有时需要双击单元格显示编辑状态&#xff0c;失去焦点时还原表格显示。 实现思路&#xff1a; 在数据中增加isFocus:false.控制是否显示在table中用cell-dblclick双击方法 先看效果&#xff1a; 上源码&#xff1a;在表格模板中用scope.row…

【Linux】主机搭建 Linux服务器环境 笔记

目录 前言选择系统软件1. 用U盘装系统2. 安装 Centos7.93. 网络套件 应用软件1. ngnix2. 防火墙配置3. nodejs 后记 前言 过年买了个 mini 主机当玩具玩一下&#xff0c;这里记录下。 选择 已有主力机 (windows) 的情况下&#xff0c;使用过如下四种 Linux宿主环境。这里总…

数据结构-邻接链表

介绍 邻接矩阵是运用较多的一种储存图的方法&#xff0c;但如果一张网图边数较少&#xff0c;就会出现二维矩阵中大部分数据为0的情况&#xff0c;浪费储存空间 为了避免空间浪费&#xff0c;也可以采用数组与链表结合的方式来存储图 假设有这样一张图 我们可以先用一个数组…

关于本地docker启动xxl-job

之前通过github拉取xxl-job到本地启动&#xff0c;已经验证完了&#xff0c;主要要记住以下几个步骤: 1.拉取代码 GitHub地址&#xff1a;https://github.com/xuxueli/xxl-job Gitee地址&#xff1a;https://gitee.com/xuxueli0323/xxl-job 2.idea打开&#xff0c;找到tabl…

鸿蒙Next怎么升级,有便捷的方法?

早在2023年11月&#xff0c;市场上有自媒体博主表示&#xff0c;华为HarmonyOS NEXT的升级计划是2X年底到2X年初完成一亿部&#xff0c;2X年底完成三亿部。虽然该博主没有明确具体年份&#xff0c;但预计是2024年底2025年初升级一亿部HarmonyOS NEXT设备&#xff0c;2025年底完…

java数据结构与算法刷题-----LeetCode503. 下一个更大元素 II

java数据结构与算法刷题目录&#xff08;剑指Offer、LeetCode、ACM&#xff09;-----主目录-----持续更新(进不去说明我没写完)&#xff1a;https://blog.csdn.net/grd_java/article/details/123063846 解题思路&#xff1a;时间复杂度和空间复杂度都是O(n) 此题是739题的衍生题…

2.19C语言学习

关于memset和0x3f int a[100]; memset(a,0x3f,sizeof(a) ); 0x3f0011 111163 C中int型变量所占的位数为4个字节&#xff0c;即32位 0x3f显然不是int型变量中单个字节的最大值&#xff0c;应该是0x7f0111 1111 B 那为什么要赋值0x3f&#xff1a; 作为无穷大使用 因为4个字节均…

python在flask中的请求数据“无限流”

文章目录 一、问题描述二、解决方案 一、问题描述 在flask请求中&#xff0c;有个需求是让调用方一直调接口&#xff0c;并立马返回&#xff0c;而接口方缓存请求&#xff0c;依次执行。 二、解决方案 from flask import Flask, request, jsonify from queue import Queue i…

找到包含两/三个平面坐标点的椭圆点集

直接上代码 import numpy as np def fit_ellipse(points:np.array None, extension_ratio: float 0.2, eccentricity: float 0.8, n_points: int 100):center np.mean(points, axis0)if points.shape[0] 2:axis_vector points[1] - points[0]distance np.linalg.norm(…

K8s ingress-nginx根据请求目录不同将请求转发到不同应用

K8s ingress-nginx根据请求目录不同将请求转发到不同应用 1. 起因 有小伙伴做实验想要实现以下需求: 输入www.pana.com/app1访问app1的svc 输入www.pana.com/app2访问app2的svc 2. 实验 2.1 Dockerfile 先准备Dockerfile FROM nginx:1.20ADD index.html /usr/share/ngin…

.net6 webapi log4net完整配置使用流程

前置&#xff1a;为项目安装如下两个依赖 1.创建文件夹cfgFile 2.创建log4net.Config <?xml version"1.0" encoding"utf-8" ?> <log4net><appender name"ConsoleAppender" type"log4net.Appender.ConsoleAppender"…

Sora给中国AI带来的真实变化

OpenAI的最新技术成果——文生视频模型Sora&#xff0c;在春节假期炸裂登场&#xff0c;令海内外的AI从业者、投资人彻夜难眠。 如果你还没有关注到这个新闻&#xff0c;简单介绍一下&#xff1a;Sora是OpenAI使用超大规模视频数据&#xff0c;训练出的一个通用视觉模型&#x…

搜索中关于稀疏检索和稠密向量检索的召回效果比较

不同检索方式说明 最近在做搜索召回提升相关的研究工作。对比了稀疏检索和稠密向量检索的效果。其中使用的搜索引擎为elasticsearch8.x版本。稀疏检索包括BM25的检索方式&#xff0c;以及es官方在8.8之后版本提供的稀疏向量模型的方式。稠密向量检索&#xff0c;是指借助机器学…