下载地址:
- APP下载地址: https://wws.lanzous.com/b01tsdd7c 密码:dp1f
- Github: https://github.com/kirikaTowa/GalleryVolley
Boat
- 视频来源:https://www.bilibili.com/video/BV1sJ41127EMlongway777
- Demo学习:Http库,Volley
- 图片加载库: Glide
- 动态占位符效果:Shimmerlayout
- 下拉刷新工具:SwipeRefreshLayout
- 设置RecycleView实现每行两个的布局在这里插入图片描述
- Pixabay 开放API:
- 注意序列化的命名一定要对上,由于是自动赋值,名字对不上就拿不到数据。
https://pixabay.com/api/docs/
1. 测试Volley架构
2.添加下拉刷新
- SwipeRefreshLayout包裹内容即可,目前版本安卓自带,想导库也可以
implementation ‘androidx.swiperefreshlayout:swiperefreshlayout:1.0.0’
- 主界面操作
1)找到并声明控件
2)设置监听器,内部设置一个监听对象,拉动即可进入加载状态(抽取glide加载图片方法,完成后停止刷新)
public class MainActivity extends AppCompatActivity {
String url1="https://images.pexels.com/photos/240040/pexels-photo-240040.jpeg?auto=compress&cs=tinysrgb&h=650&w=940";
String url2="https://images.pexels.com/photos/459225/pexels-photo-459225.jpeg?auto=compress&cs=tinysrgb&dpr=3&h=750&w=1260";
private ImageView imageView;
private SwipeRefreshLayout swipeRefreshLayout;
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
swipeRefreshLayout=findViewById(R.id.swipeRefreshLayout);
imageView= findViewById(R.id.imageView)
RequestQueue mQueue = Volley.newRequestQueue(this);
swipeRefreshLayout.setOnRefreshListener(new SwipeRefreshLayout.OnRefreshListener() {
@Override
public void onRefresh() {
loadImage();
}
});
}
void loadImage() {
Random random=new Random();
String url=random.nextBoolean()? url1 :url2;//两张图几率5 5 开 该方法产生一个均匀分布的bool值
Glide.with(this)
.load(url)
.placeholder(R.drawable.ic_launcher_background)//占位符
.listener(new RequestListener<Drawable>() {
@Override
public boolean onLoadFailed(@Nullable GlideException e, Object model, Target<Drawable> target, boolean isFirstResource) {
if (swipeRefreshLayout.isRefreshing())
swipeRefreshLayout.setRefreshing(false);
return false;
}
@Override
public boolean onResourceReady(Drawable resource, Object model, Target<Drawable> target, DataSource dataSource, boolean isFirstResource) {
if (swipeRefreshLayout.isRefreshing())
swipeRefreshLayout.setRefreshing(false);
return false;
}
})
.into(imageView);
}
}
3. ViewModel测试
4. 正式开启画廊
4.1. 导包
implementation 'androidx.navigation:navigation-fragment-ktx:2.1.0'
implementation 'androidx.navigation:navigation-ui-ktx:2.1.0'
implementation 'com.google.code.gson:gson:2.2.4'
implementation 'com.android.volley:volley:1.1.1'
implementation 'io.supercharge:shimmerlayout:2.1.0'//闪动占位符
implementation 'com.github.bumptech.glide:glide:4.10.0'
implementation 'androidx.lifecycle:lifecycle-extensions:2.2.0-beta01'
implementation 'com.github.chrisbanes.photoview:library:1.2.4'//支持手势缩放
- 使用Pixabay API
4.2 代码
1.布局
- 布局建两个Fragment(基础blank类型),画册页Fragment和点开大图Fragment
class GalleryFragment : Fragment() {
override fun onCreateView(
inflater: LayoutInflater, container: ViewGroup?,
savedInstanceState: Bundle?
): View? {
// Inflate the layout for this fragment
return inflater.inflate(R.layout.fragment_gallery, container, false)
}
}
- 建立一个navigation导航,用于连接两个Fragment的跳转
1)。(res->new ->android resource file type选择navigation),点击添加两个Fragment
2)连接导航线路
3)来到MainAC的layout,把我们建立的navigation拖进来
2. 观察API
- 在FarawayPlayer中我们知道,真实数据进行了两层包裹,先将外层全定义接收,其中一个用List进行二层的多级包裹,再创建第二个类。选取所需的字段
- 建立Pixabay类对应API
data class Pixabay(//自动添加域和set get
val totalHits:Int,
val hits:Array<PhotoItem>,
val total:Int
)
data class PhotoItem(
val webformatURL:String,
val id:Int,
val largeImageURL:String
)
- 可以定制序列化接口,1)可以实现后续传递 2)平衡API元组和自定义属性。
data class Pixabay(//自动添加域和set get
val totalHits:Int,
val hits:Array<PhotoItem>,
val total:Int
) {
override fun equals(other: Any?): Boolean {
if (this === other) return true
if (javaClass != other?.javaClass) return false
other as Pixbay
if (totalHits != other.totalHits) return false
if (!hits.contentEquals(other.hits)) return false
if (total != other.total) return false
return true
}
override fun hashCode(): Int {
var result = totalHits
result = 31 * result + hits.contentHashCode()
result = 31 * result + total
return result
}
}
data class PhotoItem(
@SerializedName("webformatURL")val previewUrl:String,
@SerializedName("id")val photoId:Int,
@SerializedName("largeImageURL")val fullUrl:String
)
3. 建立第一个页面的GalleryViewModel
- 实现ViewModel和建立LiveData
- 建立Volley单例模式:VolleySingleton
class VolleySingleton private constructor(context:Context){
companion object {
private var INSTANCE : VolleySingleton?=null
fun getInstance(context: Context) =
INSTANCE?: synchronized(this) {//避免多线程碰撞
VolleySingleton(context).also { INSTANCE = it }
}
}
val requestQueue:RequestQueue by lazy {
Volley.newRequestQueue(context.applicationContext)
}
}
- Volley先建立请求,再发送通知
class GalleryViewModel(application: Application) : AndroidViewModel(application) {
//私有化vitable,开发非 Mutable
private val _photoListLive= MutableLiveData<List<PhotoItem>>()
//开放
val photoListLive:LiveData<List<PhotoItem>>
get() = _photoListLive//只能读取而不能进行数据源的更改
fun fetchData(){
val stringResult=StringRequest(
Request.Method.GET,
getUrl(),
//解析后赋值给内容
Response.Listener {//正确与失败后的监听器
// _photoListLive.value=Gson().fromJson(it,Pixabay::class.java).hits.toList()
},
Response.ErrorListener {
Log.d("hello",it.toString())
}
)//Request导Volley包
//添加volley队列
VolleySingleton.getInstance(getApplication()).requestQueue.add(stringResult)//加载 reponse会回调
}
private fun getUrl():String {
return "https://pixabay.com/api/?key=12472743-874dc01dadd26dc44e0801d61&q=${keyWords.random()}&per_page=100"
}
//关键词随机化处理
private val keyWords = arrayOf("cat", "dog", "panda", "beauty", "miku", "animal")
}
4. 回到第一个Fragment界面操作加入RecycleView
- 将其转为swiperefreshlayout,刷新
- 加入recycleview与对应条目,gallery_cell条目对应发光体和包裹图片
- 建立adapter->这里我们使用更简洁的ListAdapter,实现两个方法后发现的仍然报错-》新特性:
需要一个比较器
- 完成onCreateViewHolder,onBindViewHolder
class GalleryAdapter : ListAdapter<PhotoItem, GalleryAdapter.MyViewHolder>(DIFFCALLBACK) {
//初始化
override fun onCreateViewHolder(parent: ViewGroup, viewType: Int): MyViewHolder {
//创建holder
val holder = MyViewHolder(LayoutInflater.from(parent.context).inflate(R.layout.gallery_cell, parent, false))
holder.itemView.setOnClickListener {
/* Bundle().apply {
putParcelable("PHOTO",getItem(holder.adapterPosition))
holder.itemView.findNavController().navigate(R.id.action_galleryFragment_to_photoFragment,this)
}*/
}
return holder
}
override fun onBindViewHolder(holder: MyViewHolder, position: Int) {
holder.itemView.shimmerLayoutCell.apply {
setShimmerColor(0x55FFFFFF)
setShimmerAngle(0)//闪动角度
startShimmerAnimation()
}
Glide.with(holder.itemView)
.load(getItem(position).previewUrl)//photo对象
.placeholder(R.drawable.ic_photo_gray_24dp)
.listener(object : RequestListener<Drawable> {
override fun onResourceReady(
resource: Drawable?,
model: Any?,
target: Target<Drawable>?,
dataSource: DataSource?,
isFirstResource: Boolean
): Boolean {
return false.also { holder.itemView.shimmerLayoutCell?.stopShimmerAnimation() }//判断空, 图片未加载完全就切走 但listenrer还在监听
}
override fun onLoadFailed(
e: GlideException?,
model: Any?,
target: com.bumptech.glide.request.target.Target<Drawable>?,
isFirstResource: Boolean
): Boolean {
return false//都return false 不然不显图
}//监听加载完成后停止shimmer
}
)
.into(holder.itemView.imageView)//加载上去
}
object DIFFCALLBACK : DiffUtil.ItemCallback<PhotoItem>() {
override fun areItemsTheSame(oldItem: PhotoItem, newItem: PhotoItem): Boolean {
return oldItem === newItem //判断是否为同一个对象 三等于号
}
override fun areContentsTheSame(oldItem: PhotoItem, newItem: PhotoItem): Boolean {
return oldItem.photoId == newItem.photoId//判断内容是否相同
}
}
class MyViewHolder(itemView: View) : RecyclerView.ViewHolder(itemView)
}
添加占位符
绑定具体的List操作在submitlist实现,见下面
5. 来到GalleryFragment做整合
1)先将RecycleView进行初始化
2)以观察者方式观察list,若该lisr为空则先加载数据
class GalleryFragment : Fragment() {
override fun onCreateView(
inflater: LayoutInflater, container: ViewGroup?,
savedInstanceState: Bundle?
): View? {
// Inflate the layout for this fragment
return inflater.inflate(R.layout.fragment_gallery, container, false)
}
override fun onActivityCreated(savedInstanceState: Bundle?) {
super.onActivityCreated(savedInstanceState)
val galleryAdapter=GalleryAdapter()
recycleView.apply{
adapter=galleryAdapter
layoutManager= GridLayoutManager(requireContext(),2)//两列
}
//创建一个观察
val galleryViewModel= ViewModelProvider(this).get(GalleryViewModel::class.java)
//创建一个观察 提交装配一下
galleryViewModel.photoListLive.observe(this, Observer {
galleryAdapter.submitList(it)//listadapter submit即可更新数据
//接收到数据就关闭刷新栏
// swipeLayoutGallery.isRefreshing = false
})
//如果是空的 直接加载内容 不用手动拉一下
galleryViewModel.photoListLive.value?:galleryViewModel.fetchData()
//设置下拉获取数据
}
}
- 添加权限
<uses-permission android:name="android.permission.INTERNET"/>
- 运行报错,Viewmodel初始化问题,因为这次继承的是AndroidViewModel,声明需factory,且有参数
//创建一个观察
val galleryViewModel=
ViewModelProvider(this,ViewModelProvider.AndroidViewModelFactory(requireActivity().application)).get(GalleryViewModel::class.java)
- 增加下拉刷新和加载成功后消失,Viewmodel就不用写接口了
- 加入右上角menu
1)增设menu文件
2)GalleryFragment复写Menu方法
3)onActivityCreated中设置占位为true
override fun onCreateOptionsMenu(menu: Menu, inflater: MenuInflater) {
super.onCreateOptionsMenu(menu, inflater)
inflater.inflate(R.menu.menu,menu)
}
- 设置menu监听事件,这里需要viewmodel,就把其放到最外面惰性加载
private lateinit var galleryViewModel: GalleryViewModel
override fun onOptionsItemSelected(item: MenuItem): Boolean {
when(item.itemId){
R.id.swipeIndicator -> {
swiperefreshlayout.isRefreshing = true
galleryViewModel.fetchData()
}
}
return super.onOptionsItemSelected(item)
}
- 发现转的很快就消失了,界面不是很友好,利用
handler进行延迟加载
5. 处理两个Fragment的跳转
-
回到adapter的点击事件,利用Bundle和
Parcelabel接口(序列化)
传递数据。之前的FarawayPlay是利用Intent进行传递的,且点击事件是在Activity中。
1)使用apply将我们所需要的序列化数据put进我们定义的名字中,利用导航控制器确定跳转及传递Bundle。
2)若有switch树形的跳转navigation,则我们可以多些几个导航跳转,然后设置不同的导航控制器
-
之前实现序列化接口都要实现其对应方法;build.gradle中加入experimental=true;
增加@Parcelize标注即可;
override fun onCreateViewHolder(parent: ViewGroup, viewType: Int): MyViewHolder {
//创建holder
val holder = MyViewHolder(LayoutInflater.from(parent.context).inflate(R.layout.gallery_cell, parent, false))
holder.itemView.setOnClickListener {
Bundle().apply {
putParcelable("PHOTO",getItem(holder.adapterPosition))
holder.itemView.findNavController().navigate(R.id.action_galleryFragment_to_photoFragment,this)//this指创建的Bundle
}
}
return holder
}
- 修该fragment_photo的界面
- 修改代码
1)通过arguments在Glide进行加载是获取Bundle中设定的键值对取出positon并获取图片URL
class PhotoFragment : Fragment() {
override fun onCreateView(
inflater: LayoutInflater, container: ViewGroup?,
savedInstanceState: Bundle?
): View? {
// Inflate the layout for this fragment
return inflater.inflate(R.layout.fragment_photo, container, false)
}
override fun onActivityCreated(savedInstanceState: Bundle?) {
super.onActivityCreated(savedInstanceState)
shimmerLayoutphoto.apply {
setShimmerColor(0x55FFFFFF)
setShimmerAngle(0)//闪动角度
startShimmerAnimation()
}
Glide.with(requireActivity())
.load(arguments?.getParcelable<PhotoItem>("PHOTO")?.fullUrl)//获取网址
.placeholder(R.drawable.ic_photo_gray_24dp)//设定占位符
.listener(object :RequestListener<Drawable>{//监听load的情况
override fun onLoadFailed(
e: GlideException?,
model: Any?,
target: Target<Drawable>?,
isFirstResource: Boolean
): Boolean {
return false
}
override fun onResourceReady(
resource: Drawable?,
model: Any?,
target: Target<Drawable>?,
dataSource: DataSource?,
isFirstResource: Boolean
): Boolean {
return false.also { shimmerLayoutphoto.stopShimmerAnimation() }
}
})
.into(photoView)
}
- 完善Navigation导航
class MainActivity : AppCompatActivity() {
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
setContentView(R.layout.activity_main)
NavigationUI.setupActionBarWithNavController(this,findNavController(R.id.fragment))
}
override fun onSupportNavigateUp(): Boolean {
return super.onSupportNavigateUp() || findNavController(R.id.fragment).navigateUp()
}
}
4.3 新特性,加入图片浏览的左右滑动以及图片下载
- 删除photo及相关的xml文件【delete anyway】,注意navigation中的fragment引用不会自动消失,所以要进入进行删除
- 建立新的第二个fragment文件,界面上调整gravity进行位移至最下面
- 目前该界面只是有个骨架,没有填充东西,所以新建xml,时候用viewpager适配
- 建立adapterPager->PhotoListAdapter
class PagerPhotoListAdapter: ListAdapter<PhotoItem, PagerPhotoListAdapter.PagerPhotoViewHolder>(DiffCallBack) {
object DiffCallBack : DiffUtil.ItemCallback<PhotoItem>() {
override fun areItemsTheSame(oldItem: PhotoItem, newItem: PhotoItem): Boolean {
return oldItem === newItem
}
override fun areContentsTheSame(oldItem: PhotoItem, newItem: PhotoItem): Boolean {
return oldItem.photoId == newItem.photoId
}
}
class PagerPhotoViewHolder(itemView: View) : RecyclerView.ViewHolder(itemView)
override fun onCreateViewHolder(parent: ViewGroup, viewType: Int): PagerPhotoViewHolder {
LayoutInflater.from(parent.context).inflate(R.layout.pager_photo_view, parent, false)
.apply {
return PagerPhotoViewHolder(this)
}
}
override fun onBindViewHolder(holder: PagerPhotoViewHolder, position: Int) {
Glide.with(holder.itemView)
.load(getItem(position).previewUrl)
.placeholder(R.drawable.ic_photo_gray_24dp)
.into(holder.itemView.pagerPhoto)
}
}
- 来到PagerPhotoFragment,设置数据源,需要从第一个Fragment绑定的数据传递过来
- 来到第一个Fragment对应adapter的点击事件,原fragment删了,这边跳转肯定出问题,原本传递一个Position来找对应图片,而这次还要将整个List全传递过来,并且将新Fragment加入navigation引导。
- 修改对应fragment,将数据源设定给Viewpageradapter,取出list,更新list
class PagerPhotoFragment : Fragment() {
override fun onCreateView(
inflater: LayoutInflater, container: ViewGroup?,
savedInstanceState: Bundle?
): View? {
// Inflate the layout for this fragment
return inflater.inflate(R.layout.fragment_pager_photo, container, false)
}
override fun onActivityCreated(savedInstanceState: Bundle?) {
super.onActivityCreated(savedInstanceState)
val phootList: ArrayList<PhotoItem>? =arguments?.getParcelableArrayList<PhotoItem>("PHOTO_LIST")
PagerPhotoListAdapter().apply {
viewPager2.adapter=this//二代viewpager可接受listadapter
//再给个数据
submitList(phootList)
}
}
}
即可实现左右滑动了
- 下面没有同步变化页面,所以加入viewpager监听,但有个监听机制问题,每次这个position都从0开始,然后才左右监听
viewPager2.registerOnPageChangeCallback(object :ViewPager2.OnPageChangeCallback(){
override fun onPageSelected(position: Int) {
super.onPageSelected(position)
photoTag.text="${position+1}/${phootList?.size}"
}
})
- 设定值,每次赋值,第三个参数是动画效果 即可.改主题Dark,background可直接选取,改为白字
viewPager2.setCurrentItem(arguments?.getInt("PHOTO_POSITION") ?: 0, false)
- 尝试新的layout布局,据图片大小自适应布局,并且把centcrop给去掉
- 预览,设置卡片间隔4dp,两卡片间会有8dp,卡片间设为2dp,外包裹设为2dp
- 绘制纯色矢量图
1)建立drawable的xml文件
2)画一个方块/和当时画登录注册一样,然后glide替换一下
<selector xmlns:android="http://schemas.android.com/apk/res/android">
<item>
<shape>
<solid android:color="#EFEFF4"/>
<size android:height="30dp"
android:width="40dp"/>
</shape>
</item>
</selector>
4.4 新特性,调整瀑布布局,加入点赞和收藏
- 观察API,在图片缓存前,直接确定高度
- BindViewHolder中,通过设置layoutParams的高度
holder.itemView.imageView.layoutParams.height=getItem(position).photoHeight
- 虽然成功了,但图片间隙乱七八糟的,emmmm,之前调整的centcrop在限定高度后可以使用了,使照片和容器一起收缩变化,搞定。
- 利用API的其他信息,若用户ID,喜爱和收藏的数量。
@Parcelize data class PhotoItem(
@SerializedName("webformatURL")val previewUrl:String,
@SerializedName("id")val photoId:Int,
@SerializedName("largeImageURL")val fullUrl:String,
@SerializedName("webformatHeight")val photoHeight:Int,
@SerializedName("user") val photoUser:String,
@SerializedName("likes") val photoLikes:Int,
@SerializedName("favorites") val photoFavorites:Int
):Parcelable
- 修改条目的布局,连接三个边
- 增设两个矢量图,用于反馈点赞和收藏
- 增添控件
- 使用
with语句
()内的对象写出来,后面的参数中可以不写而默认调用。.with
4.5准备下载图片
保存图片
,先建立一个矢量图。和FarawayPlayer一样建立两个颜色的,然后编辑xml按下有变化(通过Selector选择器->普通状态和按下状态)。
<?xml version="1.0" encoding="utf-8"?>
<selector xmlns:android="http://schemas.android.com/apk/res/android">
<item android:state_pressed="true" android:drawable="@drawable/ic_file_download_gray" />
<item android:drawable="@drawable/ic_file_download_white" />
</selector>
- 大图的界面是FrameLayout改为->ConstraintLayout便于布局,加入DownLoad上下拖动图片对齐。
- 保存到系统相册 公共空间,添加清单权限,比INTERNET严格(危险权限还需动态声明)29放松了,写入不需要,但读取需要。最好动态注册
<uses-permission android:name="android.permission.WRITE_EXTERNAL_STORAGE"/>
- 图片保存方式PagerPhotoFragment
1)将原图下载:网址->下载->作为图片存储
- 从显示的图片转换为位图存储(对应的ViewHolder->找到图片)
private fun savePhoto() {
val holder=(viewPager2[0] as RecyclerView).findViewHolderForAdapterPosition(viewPager2.currentItem)//获取第一个元素 转为RecycleView
as PagerPhotoListAdapter.PagerPhotoViewHolder
val bitmap=holder.itemView.pagerPhoto.drawable.toBitmap()
//API29前使用媒体资源索引管理器
if (MediaStore.Images.Media.insertImage(requireContext().contentResolver,bitmap,null,null)==null){
Toast.makeText(requireContext(), "存储失败", Toast.LENGTH_SHORT).show()
}else{
Toast.makeText(requireContext(),"存储成功",Toast.LENGTH_SHORT).show()
}
}
- 但这种方法已经别弃用了,若target版本改为>29就会报错,修改分两步
val saveUri=requireContext().contentResolver.insert(MediaStore.Images.Media.EXTERNAL_CONTENT_URI,
ContentValues())?:kotlin.run {
Toast.makeText(requireContext(), "存储失败", Toast.LENGTH_SHORT).show()
return
}//里面可以空也可以具体化
//2.真正写入 use用好后会自动关闭流 90压缩率
requireContext().contentResolver.openOutputStream(saveUri).use {
if (bitmap.compress(Bitmap.CompressFormat.JPEG,90,it)==true){
Toast.makeText(requireContext(),"存储成功",Toast.LENGTH_SHORT).show()
}else{
Toast.makeText(requireContext(), "存储失败", Toast.LENGTH_SHORT).show()
}
}
class PagerPhotoFragment : Fragment() {
override fun onCreateView(
inflater: LayoutInflater, container: ViewGroup?,
savedInstanceState: Bundle?
): View? {
// Inflate the layout for this fragment
return inflater.inflate(R.layout.fragment_pager_photo, container, false)
}
override fun onActivityCreated(savedInstanceState: Bundle?) {
super.onActivityCreated(savedInstanceState)
val phootList: ArrayList<PhotoItem>? =arguments?.getParcelableArrayList<PhotoItem>("PHOTO_LIST")
PagerPhotoListAdapter().apply {
viewPager2.adapter=this//二代viewpager可接受listadapter
//再给个数据
submitList(phootList)
}
viewPager2.registerOnPageChangeCallback(object :ViewPager2.OnPageChangeCallback(){
override fun onPageSelected(position: Int) {
super.onPageSelected(position)
photoTag.text="${position+1}/${phootList?.size}"
}
})
viewPager2.setCurrentItem(arguments?.getInt("PHOTO_POSITION") ?: 0, false)
//保存到系统相册 公共空间
saveButton.setOnClickListener {
//toast()
if (Build.VERSION.SDK_INT < 29 && ContextCompat.checkSelfPermission(
requireContext(),
Manifest.permission.WRITE_EXTERNAL_STORAGE
) != PackageManager.PERMISSION_GRANTED
) {
requestPermissions(//只有一个参数也要用数组arrayof
arrayOf(Manifest.permission.WRITE_EXTERNAL_STORAGE),
1
)
}else{
savePhoto()
}
}
}
override fun onRequestPermissionsResult(
requestCode: Int,
permissions: Array<out String>,
grantResults: IntArray
) {
super.onRequestPermissionsResult(requestCode, permissions, grantResults)
when (requestCode) {
1 -> {
if (grantResults.isNotEmpty() && grantResults[0] == PackageManager.PERMISSION_GRANTED) {
savePhoto()
} else{
Toast.makeText(requireContext(), "存储失败", Toast.LENGTH_SHORT).show()
}
}
}
}
private fun savePhoto() {
val holder=(viewPager2[0] as RecyclerView).findViewHolderForAdapterPosition(viewPager2.currentItem)//获取第一个元素 转为RecycleView
as PagerPhotoListAdapter.PagerPhotoViewHolder
val bitmap=holder.itemView.pagerPhoto.drawable.toBitmap()
/* //API29前使用媒体资源索引管理器
if (MediaStore.Images.Media.insertImage(requireContext().contentResolver,bitmap,null,null)==null){
Toast.makeText(requireContext(), "存储失败", Toast.LENGTH_SHORT).show()
}else{
Toast.makeText(requireContext(),"存储成功",Toast.LENGTH_SHORT).show()
}*/
//29以后 手动制作
//1. 定义路径 占位
val saveUri=requireContext().contentResolver.insert(MediaStore.Images.Media.EXTERNAL_CONTENT_URI,
ContentValues())?:kotlin.run {
Toast.makeText(requireContext(), "存储失败", Toast.LENGTH_SHORT).show()
return
}//里面可以空也可以具体化
//2.真正写入 use用好后会自动关闭流 90压缩率
requireContext().contentResolver.openOutputStream(saveUri).use {
if (bitmap.compress(Bitmap.CompressFormat.JPEG,90,it)==true){
Toast.makeText(requireContext(),"存储成功",Toast.LENGTH_SHORT).show()
}else{
Toast.makeText(requireContext(), "存储失败", Toast.LENGTH_SHORT).show()
}
}
}
}
- 目前是放在主线程运行,当图片大时最好放入其他线程,先标识关键字,标识可以挂起。就不能直接调用方法了,指定范围
协程概念
1)指定范围
2)导包并使用协程调用,若无协程,点击保存可能会阻塞主线程,保存期间划不动界面
implementation 'androidx.lifecycle:lifecycle-runtime-ktx:2.2.0-rc03'
3)但此时Toast就无法运行了//他是主线程的UI操作,使用MainScope().launch即可
MainScope().launch { Toast.makeText(requireContext(),"存储成功",Toast.LENGTH_SHORT).show()}