【day28】宠物邀约上传流程修改 X ViewPager2 with indicator

前几天我们成功上传了多张照片,但是我们的画面有点丑丑的,所以我们今天要把它修改成更漂亮,且下面会有指标,让我们可以知道总共有几张照片,以及现在的位置!!

一、使用第三方套件 (ViewPagerIndicator)

我们这次是使用第三方套件,既然有现成的,就让我们直接使用吧! 它也有很多样式,可以让我们去自己使用。来源:https://github.com/zhpanvip/viewpagerindicator

1.先到gradle新增以下

allprojects {
		repositories {
			...
			maven { url 'https://jitpack.io' }
		}
	}
//目前最新是1.2.1,之後如果有更新,就用最新的吧!
implementation 'com.github.zhpanvip:viewpagerindicator:1.2.1'

2.新增indicator

我们直接在fragment_add_invitation的地方新增 Indicator就好,且因为它是在FrameLayout,所以最下面的view会是最上层

<FrameLayout
        android:id="@+id/fl_invitation_detail"
        android:layout_width="match_parent"
        android:layout_height="@dimen/image_height"
        app:layout_constraintTop_toBottomOf="@id/toolbar_invitation_detail_fragment"
        app:layout_constraintStart_toStartOf="parent">



        <androidx.viewpager2.widget.ViewPager2
            android:id="@+id/banner_image"
            android:layout_width="match_parent"
            android:layout_height="match_parent"/>

        <com.zhpan.indicator.IndicatorView
            android:id="@+id/indicator_add_invitation"
            android:layout_width="wrap_content"
            android:layout_height="wrap_content"
            android:layout_gravity="bottom|center"
            android:layout_margin="10dp"/>



    </FrameLayout>

3.实例化viewPager2

这边funtion我们会再等等从相簿拿到图片後,呼叫它

private  fun setAdapterWitIndicator(list: List<Uri>){

        binding.bannerImage.adapter = MultiplePhotoAdapter(list)
        binding.indicatorAddInvitation.apply {
		//设定没被选择的颜色/被选择的颜色
            setSliderColor(Color.GRAY,ContextCompat.getColor(requireContext(),R.color.pewter_blue))
            setSliderWidth(resources.getDimension(R.dimen.indicator_width))
            //设定模式(更多模式可以往下看)
			setSlideMode(IndicatorSlideMode.SCALE)
            setIndicatorStyle(IndicatorStyle.CIRCLE)
			//绑定viewPager2
                .setupWithViewPager(binding.bannerImage)

        }

    }

Mode其他样式如下

https://ithelp.ithome.com.tw/upload/images/20211013/201380170UTDrcpczV.png

有许多属性可以调整,有兴趣的小夥伴可以多去看看!

三、修改上传照片流程

我们之前在从相簿拿到照片後,我们就先把它上传到Storage了,但这不太好,若使用者三心二意一直换照片,那我们的容量就爆啦!! 所以我们会是先把它显示在UI上,若是使用者按送出的Button之後,我们在进行储存

1.修改我们的resultLauncher


private var selectedUriList: List<Uri>? = null



private val resultLauncher = registerForActivityResult(ActivityResultContracts.StartActivityForResult()){ uri ->
        if (uri.resultCode == Activity.RESULT_OK){
            val selectedUri = uri.data?.clipData
            if (selectedUri != null){

                val list = mutableListOf<Uri>()
                val account = selectedUri.itemCount
				//这边一样透过for回圈把我们拿到的result改成uri
                for (i in 0 until account){
                    val model = selectedUri.getItemAt(i).uri
                    list.add(model)
                }
								
                selectedUriList = list
//我们等等建立funtion让我们可以拿到type的名称,原本我们是在viewmodel做,但现在要改在Fragment里面先拿到file的type
				getTypeFromSelectedUri(list)
                //这边就直接用我们刚刚的套件,把list传进去,它就会帮我们生出indicator罗!
				setAdapterWitIndicator(list)

            }
        }
    }

我们直接在fragment里面先把type拿出来,并做成list,之後再传给viewModel

private fun getTypeFromSelectedUri(list: List<Uri>){

        var typeList: MutableList<String> = mutableListOf()
        for (i in 0 until list.size){
            val model = Constant.getFileExtension(requireActivity(),list[i])
            if (model != null) {
                typeList.add(model)
            }
        }
        selectedUriTypeList = typeList

    }

2.修改validDataFormAndSaveImage()

原本我们是检查转换完的uri是否为null,来判定符不符合,但是现在我们要把它改成,只要resultLauncher拿到的不是null,就给过,因为就代表它有选了嘛 XD

private fun validDataFormAndSaveImage(): Boolean{

        return when{


selectedUriList.isNullOrEmpty() ->{
                showSnackBar(resources.getString(R.string.hint_select_your_image),true)
                false
            }
			......
}

★不要全删喔,只是把图片check改成上面而已!

3.修改送出的onClick

原本的方式是,我们在onClick的时候,把invitation加入到firestore,但是现在我们要修改成,当我们把照片传上去storage,然後全部转换成功成String後,我们直接在viewModel呼叫addInvitationToFireStore(),这样就可以确保当全部转换successful的时候,就呼叫传送Invitation。

binding.btnAddInvitationFragmentSubmit ->{


               if (validDataFormAndSaveImage()){
                   showDialog(resources.getString(R.string.please_wait))
                   
					//一样先拿到UI的资料
                   val invitation = Invitation(
                       user_id = accountViewModel.userDetail.value!!.id,
                       user_name = accountViewModel.userDetail.value?.name,
                       user_image = accountViewModel.userDetail.value?.image,
                       pet_type = selectedPetType!!,
                       pet_type_description = binding.edAddInvitationPetTypeDescription.text.toString().trim(),
                       area = selectedArea!!,
                       date_place = binding.edAddInvitationDatePlace.text.toString().trim(),
                       date_time = selectedDate!!,
                       note = binding.edAddInvitationNote.text.toString().trim(),
                       update_time = Timestamp(Date(System.currentTimeMillis())),
                   )
					//再把刚刚从相簿拿来的照片list跟invitation传进去
                   selectedUriList?.let {
                       matchingViewModel.saveImageToFireStorage(selectedUriTypeList,
                           it,invitation)
                   }
               }
            }

4.修改saveImageToFireStorage

把fragment跟activity的参数拿掉,viewModel应该只要传入数据,并且透过livedata,让UI观察


//建立当图片fail的时候,可以让我们UI观察,我们不用加入successful的livedata,因为当成功的时候,它就会跑AddInvitation啦
private val _saveImage_fail = MutableLiveData<String>()
val saveImage_fail: LiveData<String>
get() = _saveImage_fail




//传入刚刚的typeList跟uriList,它们会是配对的,所以我们可以直接跑for回圈
fun saveImageToFireStorage( typeList: List<String>,uriList: List<Uri>,invitation: Invitation) {


        val newList = mutableListOf<String>()
       
       for (i in 0 until typeList.size){
           
           val sdf: StorageReference = FirebaseStorage.getInstance().reference.child(
               Constant.PET_IMAGE + "_" + System.currentTimeMillis() + "_" + typeList[i]
           )
           sdf.putFile(uriList[i])
               .addOnSuccessListener { it ->
                   it.metadata?.reference?.downloadUrl
                       ?.addOnSuccessListener { uri ->
                       val uriString = uri.toString()
                       newList.add(uriString)
				
//当所有的list都跑完後,我们就把刚刚的photoList加进去啦		
                           if (i == uriList.size-1){
                               val newInvitation = Invitation(
                                   user_id = invitation.user_id,
                                   user_name = invitation.user_name,
                                   user_image = invitation.user_image,
                                   pet_type = invitation.pet_type,
                                   pet_type_description = invitation.pet_type_description,
                                   area = invitation.area,
                                   date_place = invitation.date_place,
                                   date_time = invitation.date_time,
                                   note = invitation.note,
                                   update_time = invitation.update_time,
                                   photoUriList = newList
                               )
                               addInvitationToFireStore(newInvitation)
                           }

                       }
                       ?.addOnFailureListener {
                        _saveImage_fail.postValue(it.toString())
                            
                       }


               }
               .addOnFailureListener {
                   _saveImage_fail.postValue(it.toString())


               }
       }

    }


fun resetSaveImageState(){
        _saveImage_fail.postValue(null)
    }

## 5.修改addInvitationToFireStore()
基本上就是新增livedata,让我们UI可以观察是上传成功了吗? 还是没有


private val _invitation_add_state = MutableLiveData<Boolean>()
val invitation_add_state: LiveData<Boolean>
get() = _invitation_add_state





fun addInvitationToFireStore(invitation: Invitation) {
   
        Firebase.firestore.collection(Constant.INVITATION)
            .add(invitation)
            .addOnSuccessListener {
                val mHashMap = HashMap<String, Any>()
                mHashMap[Constant.ID] = it.id
                it.update(mHashMap)
                    .addOnSuccessListener {
                        _invitation_add_state.postValue(true)
                    }
                    .addOnFailureListener {
                        _invitation_add_state.postValue(false)

                    }
            }
            .addOnFailureListener {
                _invitation_add_state.postValue(false)

            }
    }

//我们也需要reset我们的livedata喔,不然会一直观察到之前的数据
fun resetAddInvitationState(){
        _invitation_add_state.postValue(null)
    }

6.回到UI观察,并且要reset资料

matchingViewModel.invitation_add_state.observe( viewLifecycleOwner, androidx.lifecycle.Observer {
            if (it == true){
                hideDialog()
                showSnackBar(resources.getString(R.string.add_invitation_successful),false)
                findNavController().navigate(R.id.action_addInvitationFragment_to_navigation_home)

            }else{
                hideDialog()
                showSnackBar(resources.getString(R.string.add_invitation_fail),true)
            }
            matchingViewModel.resetAddInvitationState()
        })

还有观察我们的SaveImageState

matchingViewModel.saveImage_fail.observe(viewLifecycleOwner, androidx.lifecycle.Observer {
            showSnackBar(it,true)
            matchingViewModel.resetSaveImageState()
        })

四、修改homeFragment跟dashboardfragment

我们原本是单张照片,现在则是多张照片,简单!! 我们直接在各Adapter里面的ViewHolder里面修改原本的方式,改成以下就好!! 让我们显示第一张照片

item.photoUriList?.get(0)?.let { Constant.loadPetImage(it,binding.ivDashboardInvitationItemListImage) }

大功告成啦!!

那DetailFragment基本上就一样的方式啦,这边就不多说啦!

day28.finish


<<:  【Day 28】Google Apps Script - API Blueprint 篇 - Apiary 测试 API 介绍

>>:  Day30_哇呜~最後一天的铁人实了~2021/10/13

Day 26 初学者补给站 学习方向讨论

大家好~~欢迎来到第二十六篇 聊聊学习方向讨论 本篇呢 会跟大家分享,平时本人会怎麽训练自己的程序。...

【设计+切版30天实作】|Day24 - Steps区块 - 如何做出渐层背景?

前面完成了「Pros」区块,今天来完成「Steps」的区块。 数据收集 标题的样式 Font-we...

[Day27]- 新手的Web系列CRLF 0x2

Day27- 新手的Web系列CRLF 0x2 正文 CRLF Injection原理 HTTP H...

铁人赛30天心得感想

顺利骗完30天,原来要认真骗还是可以写完30天的(X,不过今年是第一次参加,之後再参加可能不要写的太...

Day 28 - Learned Index实作(2)

延续昨天的实作,首先我们先来修改一下昨天建置的 Learned Index 类别,还有一些参数需要储...