【Android-Kotlin-Volley】图片画廊学习笔记
下载地址:
- 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;@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'//支持手势缩放
- 使用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 fragmentreturn 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 getval 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 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
- 实现ViewModel和建立LiveData
- 建立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)}
}
- 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
- 将其转为swiperefreshlayout,刷新
- 加入recycleview与对应条目,gallery_cell条目对应发光体和包裹图片
- 建立adapter->这里我们使用更简洁的ListAdapter,实现两个方法后发现的仍然报错-》新特性:
需要一个比较器
- 完成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()//设置下拉获取数据}
}
- 添加权限
<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 = truegalleryViewModel.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 {//创建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}
- 修该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 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)}
- 完善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 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)}}}
即可实现左右滑动了
- 下面没有同步变化页面,所以加入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)//获取第一个元素 转为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()}}
- 但这种方法已经别弃用了,若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)指定范围
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】图片画廊学习笔记相关推荐
- Android开源项目SlidingMenu本学习笔记(两)
我们已经出台SlidingMenu使用:Android开源项目SlidingMenu本学习笔记(一个),接下来再深入学习下.依据滑出项的Menu切换到相应的页面 文件夹结构: 点击Bluetooth能 ...
- Android Jetpack Components of LiveData 学习笔记
Android Jetpack Components of Lifecycle 学习笔记 Android Jetpack Components of LiveData 学习笔记 Android Jet ...
- Android Jetpack Components of ViewModel 学习笔记
Android Jetpack Components of Lifecycle 学习笔记 Android Jetpack Components of LiveData 学习笔记 Android Jet ...
- android spi读写不通,Android-SPI学习笔记
概述 SPI(Service Provider Interface, 服务提供方接口),服务通常是指一个接口或者一个抽象类,服务提供方是对这个接口或者抽象类的具体实现,由第三方来实现接口提供具体的服务 ...
- 《Android开发高手课》学习笔记
最近在学习张绍文老师的<Android开发高手课>课程,学习到了很多的干货,特别是在处理问题的策略和知识的广度方面给了我很多的启发,对未来的学习也提供了方向. 目前,技术的发展有两个趋势. ...
- Android工程师进阶34讲学习笔记
最近发现一个技术提升的平台,很多课程对于技术提升都多有益处,很多是实际上的项目实战并对底层原理讲解透彻.前几个月已经学习完了<Android 工程师进阶 34 讲>,现在重学一遍并做些总结 ...
- Android底层HAL驱动开发学习笔记
2017.3.27 1.确定任务:加快摄像头的信息获取速率:缩减驱动程序.减小帧的大小,缩减一个像素点的比特流(6/8/10bit) 2.重点了解函数:camerabuffer *buffer=mPr ...
- Android json解析有关内容学习笔记
http://www.2cto.com/kf/201301/185026.html 毋庸置疑,Json是当下最主流最受欢迎的数据交换格式,得益于json的简单易用,一直没有系统的学习有关json的知识 ...
- Android推送进阶课程学习笔记
今天在慕课网学习了Android进阶课程推送的server端处理回执的消息 . 这集课程主要介绍了,当server往client推送消息的时候,client须要发送一个回执回来确认收到了推送消息才算一 ...
最新文章
- 1元体验微软公有云,看起来还不错
- vue异步数据 报错_vue中异步请求数据,异步请求还没完成,文件就执行了就会报错,怎么解决?...
- SimNIBS一款无创脑刺激仿真软件安装
- Boost:基于Boost的优先调度器程序
- VS2017中的附加到进程
- jersey客户端_项目学生:带有Jersey的Web服务客户端
- 冒泡排序 快速排序 插入排序 选择排序
- [html] 当html中使用map标签时,area中coords值如何精确定位呢?
- python o创建文件_Python 文件I/O
- 实战 | Element UI 父子组件传值与事件绑定(正向)
- android 输出字节数组,Android蓝牙通信字节数组的数据类型转换 求教!
- Linux内存管理之内存寻址:分段机制的实现方式
- 如何把你的Linux系统变得更加安全
- unison与fswatch文件同步
- css设置三角形以及三角形的旋转
- 中国招聘网站调研报告
- 给文字上加中划线_小小招式让你给文字添加上划线
- DNS服务器配置:DNS服务器配置:正反解析,主从服务器,子域授权,
- vue3中使用canvas
- CSS生成关闭叉叉图标