重铸安卓荣光——上传图片组件
痛点:
公司打算做安卓软件,最近在研究安卓,打算先绘制样式
研究发现安卓并不像前端有那么多组件库,甚至有些基础的组件都需要自己实现,记录一下自己实现的组件
成品展示
一个上传图片的组件
-
可以选择拍照或者从相册中上传
-
上传可以限制数量
-
上传后可以选择某张图片删除

引入依赖
在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.UploadLayoutandroid: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可以实现自动换行,当我们插入的图片占满一行后,会自己换行,如下所示

加载添加按钮
初始化时需要把灰色的添加按钮加载进来
限制最大照片数
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()); // 生成一个唯一的 IDuploadPhotoTextView.setWidth(weight); // 设置宽度为 80dpuploadPhotoTextView.setHeight(weight); // 设置高度为 80dpuploadPhotoTextView.setGravity(Gravity.CENTER); // 设置文本居中对齐uploadPhotoTextView.setBackground(ContextCompat.getDrawable(getContext(), R.color.viewfinder_text_color4)); // 设置背景颜色uploadPhotoTextView.setText("+"); // 设置文本内容为 "+"uploadPhotoTextView.setTextSize(30); // 设置文本大小为 30dpuploadPhotoTextView.setOnClickListener(new View.OnClickListener() {@Overridepublic void onClick(View v) {// 在这里添加点击事件的逻辑uploadPhoto(v);}});// 设置控件的布局参数,可以根据需要设置边距等LinearLayout.LayoutParams layoutParams = new LinearLayout.LayoutParams(weight,weight);layoutParams.setMargins(0, 0, 10, 10); // 设置右边距为 10dp,底边距为 10dpuploadPhotoTextView.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.UploadLayoutandroid: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
RelativeLayout中ImageView用来显示图片,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 加载大图到 PhotoViewGlide.with(this).load(pictureBean.isCut() ? pictureBean.getPath() : pictureBean.getUri()) // 替换为您的大图 URL.into(photoView);// 点击大图时关闭 DialogphotoView.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
<RelativeLayoutandroid:layout_width="match_parent"android:layout_height="wrap_content"android:padding="15dp"><TextViewandroid: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"/><RelativeLayoutandroid:layout_width="match_parent"android:layout_height="wrap_content"android:layout_below="@+id/title"android:layout_marginHorizontal="30dp"><Buttonandroid: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"/><Buttonandroid: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><rotateandroid:fromDegrees="45"android:toDegrees="45"><shape android:shape="line"><stroke android:width="1dp" android:color="#FFFFFF" /></shape></rotate></item><item><rotateandroid: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;@Overrideprotected void onCreate(Bundle savedInstanceState) {super.onCreate(savedInstanceState);setContentView(R.layout.activity_main);uploadLayout = findViewById(R.id.uploadLayout);}@Overrideprotected void onActivityResult(int requestCode, int resultCode, @Nullable Intent data) {super.onActivityResult(requestCode, resultCode, data);uploadLayout.handleActivityResult(requestCode,resultCode,data);}
}
补充
昨天看了一下,每次使用的时候都要手动指定自动换行(app开头的这些属性),非常麻烦,可以在初始化的时候将这些属性设置进去
<com.example.androidtest.test.UploadLayoutandroid: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);
相关文章:
重铸安卓荣光——上传图片组件
痛点: 公司打算做安卓软件,最近在研究安卓,打算先绘制样式 研究发现安卓并不像前端有那么多组件库,甚至有些基础的组件都需要自己实现,记录一下自己实现的组件 成品展示 一个上传图片的组件 可以选择拍照或者从相册中…...
Bert基础(四)--解码器(上)
1 理解解码器 假设我们想把英语句子I am good(原句)翻译成法语句子Je vais bien(目标句)。首先,将原句I am good送入编码器,使编码器学习原句,并计算特征值。在前文中,我们学习了编…...
Visual Studio快捷键记录
日常使用Visual Studio进行开发,记录一下常用的快捷键: 复制:CtrlC剪切:CtrlX粘贴:CtrlV删除:CtrlL撤销:CtrlZ反撤销:CtrlY查找:CtrlF/CtrlI替换:CtrlH框式选…...
分享84个Html个人模板,总有一款适合您
分享84个Html个人模板,总有一款适合您 84个Html个人模板下载链接:https://pan.baidu.com/s/1GXUZlKPzmHvxtO0sm3gHLg?pwd8888 提取码:8888 Python采集代码下载链接:采集代码.zip - 蓝奏云 学习知识费力气,收集…...
vue使用.sync和update实现父组件与子组件数据绑定的案例
在 Vue 中,.sync 是一个用于实现双向数据绑定的特殊修饰符。它允许父组件通过一种简洁的方式向子组件传递一个 prop,并在子组件中修改这个 prop 的值,然后将修改后的值反馈回父组件,实现双向数据绑定。 使用 .sync 修饰符的基本语…...
C语言系列15——C语言的安全性与防御性编程
目录 写在开头1 缓冲区溢出:如何防范与处理1.1 缓冲区溢出的原因1.2 预防与处理策略 2. 安全的字符串处理函数与使用技巧2.1 strncpy函数2.2 snprintf函数2.3 strlcpy函数2.4 使用技巧 3 防御性编程的基本原则与实际方法3.1 基本原则3.2 实际方法 写在最后 写在开头…...
objectMapper、ObjectNode、JsonNode调用接口时进行参数组装
objectMapper、ObjectNode、JsonNode用于调用接口时进行参数组装 public String sendText( List< String > listUser, String content ) throws JsonProcessingException{if ( listUser.size() < 0 ){return "用户ID为空!";}if ( content.lengt…...
2024开年,手机厂商革了自己的命
文|刘俊宏 编|王一粟 2024开年,AI终端的号角已经由手机行业吹响。 OPPO春节期间就没闲着,首席产品官刘作虎在大年三十就迫不及待地宣布,OPPO正式进入AI手机时代。随后在开年后就紧急召开了AI战略发布会,…...
【安全】大模型安全综述
大模型相关非安全综述 LLM演化和分类法 A survey on evaluation of large language models,” arXiv preprint arXiv:2307.03109, 2023.“A survey of large language models,” arXiv preprint arXiv:2303.18223, 2023.“A survey on llm-gernerated text detection: Necess…...
Stable Diffusion 模型分享:AstrAnime(Astr动画)
本文收录于《AI绘画从入门到精通》专栏,专栏总目录:点这里。 文章目录 模型介绍生成案例案例一案例二案例三案例四案例五 下载地址 模型介绍 AstrAnime 是一个动漫模型,画风色彩鲜明,擅长绘制漂亮的小姐姐。 条目内容类型大模型…...
【GPTs分享】每日GPTs分享之Canva
简介 Canva,旨在帮助用户通过Canva的用户友好设计平台释放用户的创造力。无论用户是想设计海报、社交媒体帖子还是商业名片,Canva都在这里协助用户将创意转化为现实。 主要功能 设计生成:根据用户的描述和创意需求,生成定制的设…...
【机器学习】数据清洗——基于Pandas库的方法删除重复点
🎈个人主页:豌豆射手^ 🎉欢迎 👍点赞✍评论⭐收藏 🤗收录专栏:机器学习 🤝希望本文对您有所裨益,如有不足之处,欢迎在评论区提出指正,让我们共同学习、交流进…...
顺序表增删改查(c语言)
main函数: #include <stdio.h>#include "./seq.h"int main(int argc, const char *argv[]){SeqList* list create_seqList();insert_seqList(list,10);insert_seqList(list,100);insert_seqList(list,12);insert_seqList(list,23);show_seqList(l…...
MyBatis Plus中的动态表名实践
随着数据库应用的不断发展,面对复杂多变的业务需求,动态表名的处理变得愈发重要。在 MyBatis Plus(以下简称 MP)这一优秀的基于 MyBatis 的增强工具的支持下,我们可以更便捷地应对动态表名的挑战。本文将深入研究如何在…...
JAVA IDEA 项目打包为 jar 包详解
前言 如下简单 maven 项目,现在 maven 项目比较流行,你还没用过就OUT了。需要打包jar 先设置:点击 File > Project Structure > Artifacts > 点击加号 > 选择JAR > 选择From modules with dependencies 一、将所有依赖和模…...
概率基础——几何分布
概率基础——几何分布 介绍 在统计学中,几何分布是描述了在一系列独立同分布的伯努利试验中,第一次成功所需的试验次数的概率分布。在连续抛掷硬币的试验中,每次抛掷结果为正面向上的概率为 p p p,反面向上的概率为 1 − p 1-p …...
JavaScript的内存管理与垃圾回收
前言 JavaScript提供了高效的内存管理机制,它的垃圾回收功能是自动的。在我们创建新对象、函数、原始类型和变量时,所有这些编程元素都会占用内存。那么JavaScript是如何管理这些元素并在它们不再使用时清理它们的呢? 在本节中,…...
Neo4j导入数据之JAVA JDBC
目录结构 前言设置neo4j外部访问代码整理maven 依赖java 代码 参考链接 前言 公司需要获取neo4j数据库内容进行数据筛查,neo4j数据库咱也是头一次基础,辛辛苦苦安装好整理了安装neo4j的步骤,如今又遇到数据不知道怎么创建,关关难…...
LeetCode 2878.获取DataFrame的大小
DataFrame players: ------------------- | Column Name | Type | ------------------- | player_id | int | | name | object | | age | int | | position | object | | … | … | ------------------- 编写一个解决方案,计算并显示 players 的 行数和列数。 将结…...
索引失效的 12 种情况
目录 一、未使用索引字段进行查询 二、索引列使用了函数或表达式 三、使用了不等于(! 或 <>)操作符 四、LIKE 操作符的模糊查询 五、对索引列进行了数据类型转换 六、使用 OR 连接多个条件 七、表中数据量较少 八、索引列上存在大量重复值…...
论文解读:交大港大上海AI Lab开源论文 | 宇树机器人多姿态起立控制强化学习框架(二)
HoST框架核心实现方法详解 - 论文深度解读(第二部分) 《Learning Humanoid Standing-up Control across Diverse Postures》 系列文章: 论文深度解读 + 算法与代码分析(二) 作者机构: 上海AI Lab, 上海交通大学, 香港大学, 浙江大学, 香港中文大学 论文主题: 人形机器人…...
测试markdown--肇兴
day1: 1、去程:7:04 --11:32高铁 高铁右转上售票大厅2楼,穿过候车厅下一楼,上大巴车 ¥10/人 **2、到达:**12点多到达寨子,买门票,美团/抖音:¥78人 3、中饭&a…...
使用van-uploader 的UI组件,结合vue2如何实现图片上传组件的封装
以下是基于 vant-ui(适配 Vue2 版本 )实现截图中照片上传预览、删除功能,并封装成可复用组件的完整代码,包含样式和逻辑实现,可直接在 Vue2 项目中使用: 1. 封装的图片上传组件 ImageUploader.vue <te…...
Qt Http Server模块功能及架构
Qt Http Server 是 Qt 6.0 中引入的一个新模块,它提供了一个轻量级的 HTTP 服务器实现,主要用于构建基于 HTTP 的应用程序和服务。 功能介绍: 主要功能 HTTP服务器功能: 支持 HTTP/1.1 协议 简单的请求/响应处理模型 支持 GET…...
HBuilderX安装(uni-app和小程序开发)
下载HBuilderX 访问官方网站:https://www.dcloud.io/hbuilderx.html 根据您的操作系统选择合适版本: Windows版(推荐下载标准版) Windows系统安装步骤 运行安装程序: 双击下载的.exe安装文件 如果出现安全提示&…...
MySQL中【正则表达式】用法
MySQL 中正则表达式通过 REGEXP 或 RLIKE 操作符实现(两者等价),用于在 WHERE 子句中进行复杂的字符串模式匹配。以下是核心用法和示例: 一、基础语法 SELECT column_name FROM table_name WHERE column_name REGEXP pattern; …...
【学习笔记】深入理解Java虚拟机学习笔记——第4章 虚拟机性能监控,故障处理工具
第2章 虚拟机性能监控,故障处理工具 4.1 概述 略 4.2 基础故障处理工具 4.2.1 jps:虚拟机进程状况工具 命令:jps [options] [hostid] 功能:本地虚拟机进程显示进程ID(与ps相同),可同时显示主类&#x…...
人机融合智能 | “人智交互”跨学科新领域
本文系统地提出基于“以人为中心AI(HCAI)”理念的人-人工智能交互(人智交互)这一跨学科新领域及框架,定义人智交互领域的理念、基本理论和关键问题、方法、开发流程和参与团队等,阐述提出人智交互新领域的意义。然后,提出人智交互研究的三种新范式取向以及它们的意义。最后,总结…...
JavaScript基础-API 和 Web API
在学习JavaScript的过程中,理解API(应用程序接口)和Web API的概念及其应用是非常重要的。这些工具极大地扩展了JavaScript的功能,使得开发者能够创建出功能丰富、交互性强的Web应用程序。本文将深入探讨JavaScript中的API与Web AP…...
GitFlow 工作模式(详解)
今天再学项目的过程中遇到使用gitflow模式管理代码,因此进行学习并且发布关于gitflow的一些思考 Git与GitFlow模式 我们在写代码的时候通常会进行网上保存,无论是github还是gittee,都是一种基于git去保存代码的形式,这样保存代码…...
