下载地址:

  1. APP下载地址: https://wws.lanzous.com/b01tsdd7c 密码:dp1f
  2. Github: https://github.com/kirikaTowa/GalleryVolley

Boat

  1. 视频来源:https://www.bilibili.com/video/BV1sJ41127EMlongway777
  2. Demo学习:Http库,Volley
  3. 图片加载库: Glide
  4. 动态占位符效果:Shimmerlayout
  5. 下拉刷新工具:SwipeRefreshLayout
  6. 设置RecycleView实现每行两个的布局在这里插入图片描述
  7. Pixabay 开放API:
  8. 注意序列化的命名一定要对上,由于是自动赋值,名字对不上就拿不到数据。
https://pixabay.com/api/docs/


1. 测试Volley架构

2.添加下拉刷新

  1. SwipeRefreshLayout包裹内容即可,目前版本安卓自带,想导库也可以

implementation ‘androidx.swiperefreshlayout:swiperefreshlayout:1.0.0’

  1. 主界面操作
    1)找到并声明控件
    2)设置监听器,内部设置一个监听对象,拉动即可进入加载状态(抽取glide加载图片方法,完成后停止刷新)

public class MainActivity extends AppCompatActivity {String url1="https://images.pexels.com/photos/240040/pexels-photo-240040.jpeg?auto=compress&amp;cs=tinysrgb&amp;h=650&amp;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;@Overrideprotected 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() {@Overridepublic 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>() {@Overridepublic boolean onLoadFailed(@Nullable GlideException e, Object model, Target<Drawable> target, boolean isFirstResource) {if (swipeRefreshLayout.isRefreshing())swipeRefreshLayout.setRefreshing(false);return false;}@Overridepublic 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'//支持手势缩放
  1. 使用Pixabay API

4.2 代码

1.布局

  1. 布局建两个Fragment(基础blank类型),画册页Fragment和点开大图Fragment
class GalleryFragment : Fragment() {override fun onCreateView(inflater: LayoutInflater, container: ViewGroup?,savedInstanceState: Bundle?): View? {// Inflate the layout for this fragmentreturn inflater.inflate(R.layout.fragment_gallery, container, false)}
}
  1. 建立一个navigation导航,用于连接两个Fragment的跳转
    1)。(res->new ->android resource file type选择navigation),点击添加两个Fragment

    2)连接导航线路

    3)来到MainAC的layout,把我们建立的navigation拖进来

2. 观察API

  1. 在FarawayPlayer中我们知道,真实数据进行了两层包裹,先将外层全定义接收,其中一个用List进行二层的多级包裹,再创建第二个类。选取所需的字段
  2. 建立Pixabay类对应API
data class Pixabay(//自动添加域和set getval totalHits:Int,val hits:Array<PhotoItem>,val total:Int
)data class PhotoItem(val webformatURL:String,val id:Int,val largeImageURL:String)
  1. 可以定制序列化接口,1)可以实现后续传递 2)平衡API元组和自定义属性。
data class Pixabay(//自动添加域和set getval totalHits:Int,val hits:Array<PhotoItem>,val total:Int
) {override fun equals(other: Any?): Boolean {if (this === other) return trueif (javaClass != other?.javaClass) return falseother as Pixbayif (totalHits != other.totalHits) return falseif (!hits.contentEquals(other.hits)) return falseif (total != other.total) return falsereturn true}override fun hashCode(): Int {var result = totalHitsresult = 31 * result + hits.contentHashCode()result = 31 * result + totalreturn result}
}data class PhotoItem(@SerializedName("webformatURL")val previewUrl:String,@SerializedName("id")val photoId:Int,@SerializedName("largeImageURL")val fullUrl:String)

3. 建立第一个页面的GalleryViewModel

  1. 实现ViewModel和建立LiveData
  2. 建立Volley单例模式:VolleySingleton
class VolleySingleton private constructor(context:Context){companion object {private var INSTANCE : VolleySingleton?=nullfun getInstance(context: Context) =INSTANCE?: synchronized(this) {//避免多线程碰撞VolleySingleton(context).also { INSTANCE = it }}}val requestQueue:RequestQueue by lazy {Volley.newRequestQueue(context.applicationContext)}
}
  1. Volley先建立请求,再发送通知
class GalleryViewModel(application: Application) : AndroidViewModel(application) {//私有化vitable,开发非 Mutableprivate 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

  1. 将其转为swiperefreshlayout,刷新
  2. 加入recycleview与对应条目,gallery_cell条目对应发光体和包裹图片

  3. 建立adapter->这里我们使用更简洁的ListAdapter,实现两个方法后发现的仍然报错-》新特性:需要一个比较器


  4. 完成onCreateViewHolder,onBindViewHolder

class GalleryAdapter : ListAdapter<PhotoItem, GalleryAdapter.MyViewHolder>(DIFFCALLBACK) {//初始化override fun onCreateViewHolder(parent: ViewGroup, viewType: Int): MyViewHolder {//创建holderval 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 fragmentreturn inflater.inflate(R.layout.fragment_gallery, container, false)}override fun onActivityCreated(savedInstanceState: Bundle?) {super.onActivityCreated(savedInstanceState)val galleryAdapter=GalleryAdapter()recycleView.apply{adapter=galleryAdapterlayoutManager= 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()//设置下拉获取数据}
}
  1. 添加权限
 <uses-permission android:name="android.permission.INTERNET"/>
  1. 运行报错,Viewmodel初始化问题,因为这次继承的是AndroidViewModel,声明需factory,且有参数
  //创建一个观察val galleryViewModel=ViewModelProvider(this,ViewModelProvider.AndroidViewModelFactory(requireActivity().application)).get(GalleryViewModel::class.java)

  1. 增加下拉刷新和加载成功后消失,Viewmodel就不用写接口了
  2. 加入右上角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)}

  1. 设置menu监听事件,这里需要viewmodel,就把其放到最外面惰性加载
   private lateinit var galleryViewModel: GalleryViewModel
    override fun onOptionsItemSelected(item: MenuItem): Boolean {when(item.itemId){R.id.swipeIndicator -> {swiperefreshlayout.isRefreshing = truegalleryViewModel.fetchData()}}return super.onOptionsItemSelected(item)}
  1. 发现转的很快就消失了,界面不是很友好,利用handler进行延迟加载

5. 处理两个Fragment的跳转

  1. 回到adapter的点击事件,利用Bundle和Parcelabel接口(序列化)传递数据。之前的FarawayPlay是利用Intent进行传递的,且点击事件是在Activity中。
    1)使用apply将我们所需要的序列化数据put进我们定义的名字中,利用导航控制器确定跳转及传递Bundle。
    2)若有switch树形的跳转navigation,则我们可以多些几个导航跳转,然后设置不同的导航控制器

  2. 之前实现序列化接口都要实现其对应方法;build.gradle中加入experimental=true;
    增加@Parcelize标注即可;



    override fun onCreateViewHolder(parent: ViewGroup, viewType: Int): MyViewHolder {//创建holderval 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}
  1. 修该fragment_photo的界面
  2. 修改代码
    1)通过arguments在Glide进行加载是获取Bundle中设定的键值对取出positon并获取图片URL
class PhotoFragment : Fragment() {override fun onCreateView(inflater: LayoutInflater, container: ViewGroup?,savedInstanceState: Bundle?): View? {// Inflate the layout for this fragmentreturn 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)}

  1. 完善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 新特性,加入图片浏览的左右滑动以及图片下载

  1. 删除photo及相关的xml文件【delete anyway】,注意navigation中的fragment引用不会自动消失,所以要进入进行删除
  2. 建立新的第二个fragment文件,界面上调整gravity进行位移至最下面

  3. 目前该界面只是有个骨架,没有填充东西,所以新建xml,时候用viewpager适配
  4. 建立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)}}
  1. 来到PagerPhotoFragment,设置数据源,需要从第一个Fragment绑定的数据传递过来
  2. 来到第一个Fragment对应adapter的点击事件,原fragment删了,这边跳转肯定出问题,原本传递一个Position来找对应图片,而这次还要将整个List全传递过来,并且将新Fragment加入navigation引导。

  3. 修改对应fragment,将数据源设定给Viewpageradapter,取出list,更新list
class PagerPhotoFragment : Fragment() {override fun onCreateView(inflater: LayoutInflater, container: ViewGroup?,savedInstanceState: Bundle?): View? {// Inflate the layout for this fragmentreturn 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)}}}

即可实现左右滑动了

  1. 下面没有同步变化页面,所以加入viewpager监听,但有个监听机制问题,每次这个position都从0开始,然后才左右监听
        viewPager2.registerOnPageChangeCallback(object :ViewPager2.OnPageChangeCallback(){override fun onPageSelected(position: Int) {super.onPageSelected(position)photoTag.text="${position+1}/${phootList?.size}"}})
  1. 设定值,每次赋值,第三个参数是动画效果 即可.改主题Dark,background可直接选取,改为白字
  viewPager2.setCurrentItem(arguments?.getInt("PHOTO_POSITION") ?: 0, false)


  1. 尝试新的layout布局,据图片大小自适应布局,并且把centcrop给去掉

  2. 预览,设置卡片间隔4dp,两卡片间会有8dp,卡片间设为2dp,外包裹设为2dp

  3. 绘制纯色矢量图
    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 新特性,调整瀑布布局,加入点赞和收藏

  1. 观察API,在图片缓存前,直接确定高度
  2. BindViewHolder中,通过设置layoutParams的高度
 holder.itemView.imageView.layoutParams.height=getItem(position).photoHeight
  1. 虽然成功了,但图片间隙乱七八糟的,emmmm,之前调整的centcrop在限定高度后可以使用了,使照片和容器一起收缩变化,搞定。
  2. 利用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
  1. 修改条目的布局,连接三个边
  2. 增设两个矢量图,用于反馈点赞和收藏
  3. 增添控件
  4. 使用with语句()内的对象写出来,后面的参数中可以不写而默认调用。.with

4.5准备下载图片

  1. 保存图片,先建立一个矢量图。和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>
  1. 大图的界面是FrameLayout改为->ConstraintLayout便于布局,加入DownLoad上下拖动图片对齐。
  2. 保存到系统相册 公共空间,添加清单权限,比INTERNET严格(危险权限还需动态声明)29放松了,写入不需要,但读取需要。最好动态注册
<uses-permission android:name="android.permission.WRITE_EXTERNAL_STORAGE"/>
  1. 图片保存方式PagerPhotoFragment
    1)将原图下载:网址->下载->作为图片存储
  1. 从显示的图片转换为位图存储(对应的ViewHolder->找到图片)
    private fun savePhoto() {val holder=(viewPager2[0] as RecyclerView).findViewHolderForAdapterPosition(viewPager2.currentItem)//获取第一个元素 转为RecycleViewas PagerPhotoListAdapter.PagerPhotoViewHolderval 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()}}
  1. 但这种方法已经别弃用了,若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 fragmentreturn 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(//只有一个参数也要用数组arrayofarrayOf(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)//获取第一个元素 转为RecycleViewas PagerPhotoListAdapter.PagerPhotoViewHolderval 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. 目前是放在主线程运行,当图片大时最好放入其他线程,先标识关键字,标识可以挂起。就不能直接调用方法了,指定范围协程概念

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

【Android-Kotlin-Volley】图片画廊学习笔记相关推荐

  1. Android开源项目SlidingMenu本学习笔记(两)

    我们已经出台SlidingMenu使用:Android开源项目SlidingMenu本学习笔记(一个),接下来再深入学习下.依据滑出项的Menu切换到相应的页面 文件夹结构: 点击Bluetooth能 ...

  2. Android Jetpack Components of LiveData 学习笔记

    Android Jetpack Components of Lifecycle 学习笔记 Android Jetpack Components of LiveData 学习笔记 Android Jet ...

  3. Android Jetpack Components of ViewModel 学习笔记

    Android Jetpack Components of Lifecycle 学习笔记 Android Jetpack Components of LiveData 学习笔记 Android Jet ...

  4. android spi读写不通,Android-SPI学习笔记

    概述 SPI(Service Provider Interface, 服务提供方接口),服务通常是指一个接口或者一个抽象类,服务提供方是对这个接口或者抽象类的具体实现,由第三方来实现接口提供具体的服务 ...

  5. 《Android开发高手课》学习笔记

    最近在学习张绍文老师的<Android开发高手课>课程,学习到了很多的干货,特别是在处理问题的策略和知识的广度方面给了我很多的启发,对未来的学习也提供了方向. 目前,技术的发展有两个趋势. ...

  6. Android工程师进阶34讲学习笔记

    最近发现一个技术提升的平台,很多课程对于技术提升都多有益处,很多是实际上的项目实战并对底层原理讲解透彻.前几个月已经学习完了<Android 工程师进阶 34 讲>,现在重学一遍并做些总结 ...

  7. Android底层HAL驱动开发学习笔记

    2017.3.27 1.确定任务:加快摄像头的信息获取速率:缩减驱动程序.减小帧的大小,缩减一个像素点的比特流(6/8/10bit) 2.重点了解函数:camerabuffer *buffer=mPr ...

  8. Android json解析有关内容学习笔记

    http://www.2cto.com/kf/201301/185026.html 毋庸置疑,Json是当下最主流最受欢迎的数据交换格式,得益于json的简单易用,一直没有系统的学习有关json的知识 ...

  9. Android推送进阶课程学习笔记

    今天在慕课网学习了Android进阶课程推送的server端处理回执的消息 . 这集课程主要介绍了,当server往client推送消息的时候,client须要发送一个回执回来确认收到了推送消息才算一 ...

最新文章

  1. 1元体验微软公有云,看起来还不错
  2. vue异步数据 报错_vue中异步请求数据,异步请求还没完成,文件就执行了就会报错,怎么解决?...
  3. SimNIBS一款无创脑刺激仿真软件安装
  4. Boost:基于Boost的优先调度器程序
  5. VS2017中的附加到进程
  6. jersey客户端_项目学生:带有Jersey的Web服务客户端
  7. 冒泡排序 快速排序 插入排序 选择排序
  8. [html] 当html中使用map标签时,area中coords值如何精确定位呢?
  9. python o创建文件_Python 文件I/O
  10. 实战 | Element UI 父子组件传值与事件绑定(正向)
  11. android 输出字节数组,Android蓝牙通信字节数组的数据类型转换 求教!
  12. Linux内存管理之内存寻址:分段机制的实现方式
  13. 如何把你的Linux系统变得更加安全
  14. unison与fswatch文件同步
  15. css设置三角形以及三角形的旋转
  16. 中国招聘网站调研报告
  17. 给文字上加中划线_小小招式让你给文字添加上划线
  18. DNS服务器配置:DNS服务器配置:正反解析,主从服务器,子域授权,
  19. vue3中使用canvas
  20. CSS生成关闭叉叉图标

热门文章

  1. 高性能mysql学习笔记--索引
  2. Linux 下的截屏并编辑的工具-flamshot安装及使用
  3. iOS开发 - 若把上线版本下架,重新提交新的版本,通过审核后显示下架状态
  4. 【操作系统】效率-高可用技术HA与一致性
  5. IBM获5896项专利连续18年蝉联年度冠军
  6. Mapbox矢量瓦片pbf文件信息解析
  7. 复盘 | 产品经理晋级连胜的诀窍
  8. ∞(无穷)在数学中指的是什么
  9. 【机器学习笔记】——决策树(Decision Tree)
  10. docker里面pytorch关于gloo地址声明