https://pixabay.com/api/docs/
implementation ‘androidx.swiperefreshlayout:swiperefreshlayout:1.0.0’
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);
}
}
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'//支持手势缩放
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)
}
}
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
)
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
)
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)
}
}
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")
}
需要一个比较器
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"/>
//创建一个观察
val galleryViewModel=
ViewModelProvider(this,ViewModelProvider.AndroidViewModelFactory(requireActivity().application)).get(GalleryViewModel::class.java)
override fun onCreateOptionsMenu(menu: Menu, inflater: MenuInflater) {
super.onCreateOptionsMenu(menu, inflater)
inflater.inflate(R.menu.menu,menu)
}
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进行延迟加载
回到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
}
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)
}
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()
}
}
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)
}
}
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)
<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>
holder.itemView.imageView.layoutParams.height=getItem(position).photoHeight
@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
<?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>
<uses-permission android:name="android.permission.WRITE_EXTERNAL_STORAGE"/>
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()
}
}
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()
}
}
}
}
协程概念
implementation 'androidx.lifecycle:lifecycle-runtime-ktx:2.2.0-rc03'
3)但此时Toast就无法运行了//他是主线程的UI操作,使用MainScope().launch即可
MainScope().launch { Toast.makeText(requireContext(),"存储成功",Toast.LENGTH_SHORT).show()}