iT邦幫忙

2021 iThome 鐵人賽

DAY 28
0
Mobile Development

30天建立寵物約散App-Android新手篇系列 第 28

【day28】寵物邀約上傳流程修改 X ViewPager2 with indicator

  • 分享至 

  • xImage
  •  

前幾天我們成功上傳了多張照片,但是我們的畫面有點醜醜的,所以我們今天要把它修改成更漂亮,且下面會有指標,讓我們可以知道總共有幾張照片,以及現在的位置!!

一、使用第三方套件 (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


上一篇
【day27】聊天室傳送照片
下一篇
【day29】修改ProfileFragment X (第三方套件)ImagePicker
系列文
30天建立寵物約散App-Android新手篇30
圖片
  直播研討會
圖片
{{ item.channelVendor }} {{ item.webinarstarted }} |
{{ formatDate(item.duration) }}
直播中

1 則留言

0
juck30808
iT邦研究生 1 級 ‧ 2021-10-14 12:31:46

恭喜即將邁入完賽階段~

Tom iT邦新手 4 級 ‧ 2021-10-14 19:47:44 檢舉

/images/emoticon/emoticon01.gif

我要留言

立即登入留言