ソースを参照

add customview for selected gender person during create/edit mode

MrOzOn 5 年 前
コミット
808460c494

+ 172 - 0
feature_person/src/main/java/com/mrozon/feature_person/customview/GenderSwitch.kt

@@ -0,0 +1,172 @@
+package com.mrozon.feature_person.customview
+
+import android.animation.ValueAnimator
+import android.annotation.SuppressLint
+import android.content.Context
+import android.graphics.Bitmap
+import android.graphics.Canvas
+import android.graphics.Color
+import android.graphics.Paint
+import android.os.Build
+import android.util.AttributeSet
+import android.view.GestureDetector
+import android.view.MotionEvent
+import android.view.View
+import androidx.core.content.ContextCompat
+import androidx.core.graphics.drawable.DrawableCompat
+import com.mrozon.feature_person.R
+import timber.log.Timber
+
+
+class GenderSwitch(context: Context?, attrs: AttributeSet?) : View(context, attrs) {
+
+    private val paintChecked : Paint = Paint()
+    private val paintUnchecked : Paint = Paint()
+    private var mIsMale :  Boolean = true
+
+    private var listener: OnGenderChangeListener? = null
+
+    fun isMale(): Boolean {
+        return mIsMale
+    }
+
+    fun setMale(male: Boolean) {
+        mIsMale = male
+        invalidate()
+        requestLayout()
+    }
+
+    init {
+        val typedArray = context?.obtainStyledAttributes(attrs, R.styleable.GenderSwitch)
+        try {
+            paintChecked.color = typedArray!!.getColor(R.styleable.GenderSwitch_colorChecked,
+                Color.parseColor("#03A9F4"))
+            paintUnchecked.color = typedArray.getColor(R.styleable.GenderSwitch_colorUnchecked,
+                Color.parseColor("#E0E0E0"))
+            mIsMale = typedArray.getBoolean(R.styleable.GenderSwitch_isMale, true)
+        } finally {
+            typedArray?.recycle()
+        }
+    }
+
+    fun setOnGenderChangeListener(listener: OnGenderChangeListener){
+        this.listener = listener
+    }
+
+    private val paintActive : Paint = Paint().apply {
+    }
+
+    private val paintInactive : Paint = Paint().apply {
+        alpha = 75
+    }
+
+    private var bitmapMale:Bitmap? = null
+    private var bitmapFemale:Bitmap? = null
+    private var sizeBitmap = 0
+
+    private var maleXPosition: Float = 0f
+    private var femaleXPosition: Float = 0f
+    private var currentXPosition: Float = 0f
+
+    override fun onMeasure(widthMeasureSpec: Int, heightMeasureSpec: Int) {
+        Timber.d("onMeasure")
+
+        val heightSize = MeasureSpec.getSize(heightMeasureSpec) - paddingTop - paddingBottom
+        val widthSize = (MeasureSpec.getSize(widthMeasureSpec) - 2 * paddingStart - 2 * paddingEnd) / 2.5
+        sizeBitmap = minOf(heightSize, widthSize.toInt())
+        bitmapMale = getBitmapFromVectorDrawable(context, R.drawable.ic_male_avatar,
+            sizeBitmap)
+        bitmapFemale = getBitmapFromVectorDrawable(context, R.drawable.ic_female_avatar,
+            sizeBitmap)
+        maleXPosition = sizeBitmap.toFloat()+paddingStart+paddingEnd
+        femaleXPosition = MeasureSpec.getSize(widthMeasureSpec) - maleXPosition
+        if(mIsMale)
+            currentXPosition = 0f
+        else
+            currentXPosition = maleXPosition
+        super.onMeasure(widthMeasureSpec, heightMeasureSpec)
+    }
+
+    override fun onLayout(changed: Boolean, left: Int, top: Int, right: Int, bottom: Int) {
+        Timber.d("onLayout")
+        super.onLayout(changed, left, top, right, bottom)
+    }
+
+    override fun onDraw(canvas: Canvas) {
+        Timber.d("onDraw")
+        super.onDraw(canvas)
+
+        val top = (height - sizeBitmap)/2F
+        val widthCheckedArea = width-(sizeBitmap+paddingStart+paddingEnd*1F)
+
+        canvas.apply {
+            drawRect(0f,0f,width.toFloat(), height.toFloat(), paintUnchecked)
+            drawBitmap(bitmapMale!!,(paddingStart+paddingEnd)/2f,top,paintInactive)
+            drawBitmap(bitmapFemale!!,widthCheckedArea + (paddingStart+paddingEnd)/2F,top,paintInactive)
+            drawRect(currentXPosition, 0f, currentXPosition+widthCheckedArea, height.toFloat(), paintChecked)
+
+            var bitmap = bitmapMale!!
+            if(!mIsMale)
+                bitmap = bitmapFemale!!
+            drawBitmap(bitmap,currentXPosition + (widthCheckedArea-sizeBitmap)/2,top,paintActive)
+        }
+    }
+
+    @SuppressLint("ObsoleteSdkInt")
+    private fun getBitmapFromVectorDrawable(context: Context?, drawableId: Int, width:Int): Bitmap? {
+        var drawable = ContextCompat.getDrawable(context!!, drawableId)
+        if (Build.VERSION.SDK_INT < Build.VERSION_CODES.LOLLIPOP) {
+            drawable = DrawableCompat.wrap(drawable!!).mutate()
+        }
+        val bitmap = Bitmap.createBitmap(
+            width,
+            width,
+            Bitmap.Config.ARGB_8888
+        )
+        val canvas = Canvas(bitmap)
+        drawable?.setBounds(0, 0, canvas.width, canvas.height)
+        drawable?.draw(canvas)
+        return bitmap
+    }
+
+    private val myListener =  object : GestureDetector.SimpleOnGestureListener() {
+        override fun onDown(e: MotionEvent): Boolean {
+            Timber.d("onDown: $e")
+            return true
+        }
+    }
+    private val detector: GestureDetector = GestureDetector(context, myListener)
+
+    override fun onTouchEvent(event: MotionEvent?): Boolean {
+        return detector.onTouchEvent(event).let { result ->
+            if (!result) {
+                if (event?.action == MotionEvent.ACTION_UP) {
+//                    Timber.d("ACTION_UP: $event")
+                    mIsMale = !mIsMale
+                    listener?.onMaleSelected(mIsMale)
+                    val startValueAnimator = currentXPosition
+                    var endValueAnimator = 0f
+                    if(!mIsMale){
+//                        startValueAnimator = 0f
+                        endValueAnimator = maleXPosition
+                    }
+                    val valueAnimator = ValueAnimator.ofFloat(startValueAnimator, endValueAnimator).apply {
+//                        duration = 1000
+                        addUpdateListener {
+                            currentXPosition = it.animatedValue as Float
+//                            Timber.d("animatedValue=${it.animatedValue}")
+                            invalidate()
+                        }
+                    }
+                    valueAnimator.start()
+                    true
+                } else false
+            } else true
+        }
+    }
+
+    interface OnGenderChangeListener {
+        fun onMaleSelected(male: Boolean)
+    }
+
+}

+ 31 - 36
feature_person/src/main/java/com/mrozon/feature_person/presentation/EditPersonFragment.kt

@@ -2,20 +2,18 @@ package com.mrozon.feature_person.presentation
 
 import android.app.AlertDialog
 import android.content.Context
-import android.content.DialogInterface
 import android.os.Bundle
 import android.view.*
 import android.widget.EditText
 import androidx.core.app.ActivityCompat.invalidateOptionsMenu
-import androidx.core.view.get
 import androidx.fragment.app.viewModels
 import androidx.lifecycle.Observer
 import androidx.lifecycle.ViewModelProvider
 import androidx.navigation.fragment.findNavController
 import com.mrozon.core_api.entity.Gender
 import com.mrozon.core_api.navigation.EditPersonNavigator
-import com.mrozon.core_api.navigation.ListPersonNavigator
 import com.mrozon.feature_person.R
+import com.mrozon.feature_person.customview.GenderSwitch
 import com.mrozon.feature_person.databinding.FragmentEditPersonBinding
 import com.mrozon.feature_person.di.EditPersonFragmentComponent
 import com.mrozon.utils.base.BaseFragment
@@ -23,7 +21,6 @@ import com.mrozon.utils.extension.*
 import com.mrozon.utils.network.Result
 import kotlinx.coroutines.ExperimentalCoroutinesApi
 import kotlinx.coroutines.FlowPreview
-import timber.log.Timber
 import java.util.*
 import javax.inject.Inject
 
@@ -58,16 +55,17 @@ class EditPersonFragment : BaseFragment<FragmentEditPersonBinding>() {
     override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
         super.onViewCreated(view, savedInstanceState)
 
-        binding?.radioGroup?.setOnCheckedChangeListener { _, id ->
-            if(id==R.id.rbMale)
-                viewModel.setMaleGender(true)
-            else
-                viewModel.setMaleGender(false)
-        }
+        binding?.genderSwitch?.setOnGenderChangeListener(object : GenderSwitch.OnGenderChangeListener{
+            override fun onMaleSelected(male: Boolean) {
+                viewModel.setMaleGender(male)
+            }
+        })
+
+        binding?.genderSwitch?.isMale()
 
         binding?.etPersonName?.offer(viewModel.personNameChannel)
 
-        binding?.pickerBoD?.init(2020,1,1)
+        binding?.pickerBoD?.init(2020, 1, 1)
         { _, year, month, day ->
             val date = "$year-${month+1}-$day"
 //            Timber.d("string = $date date=${date.toSimpleDate().toDateString()}")
@@ -75,11 +73,13 @@ class EditPersonFragment : BaseFragment<FragmentEditPersonBinding>() {
         }
         val calendar = Calendar.getInstance()
         calendar.time = Date()
-        binding?.pickerBoD?.updateDate(calendar.get(Calendar.YEAR),
+        binding?.pickerBoD?.updateDate(
+            calendar.get(Calendar.YEAR),
             calendar.get(Calendar.MONTH),
-            calendar.get(Calendar.DAY_OF_MONTH))
+            calendar.get(Calendar.DAY_OF_MONTH)
+        )
 
-        val id = arguments?.getLong("id",0)?:0
+        val id = arguments?.getLong("id", 0)?:0
         if(id>0){
             viewModel.initValue(id)
         }
@@ -88,19 +88,13 @@ class EditPersonFragment : BaseFragment<FragmentEditPersonBinding>() {
     @ExperimentalCoroutinesApi
     @FlowPreview
     override fun subscribeUi() {
-        viewModel.male.observe(viewLifecycleOwner, Observer { isMale ->
-            if (isMale)
-                binding?.ivGenger?.setImageResource(R.drawable.ic_male_avatar)
-            else
-                binding?.ivGenger?.setImageResource(R.drawable.ic_female_avatar)
-        })
 
         viewModel.enableAdding.observe(viewLifecycleOwner, Observer { enabled ->
             invalidateOptionsMenu(activity)
         })
 
         viewModel.person.observe(viewLifecycleOwner, Observer { result ->
-            if(result!=null){
+            if (result != null) {
                 when (result.status) {
                     Result.Status.LOADING -> {
                         binding?.progressBar?.visible(true)
@@ -118,7 +112,7 @@ class EditPersonFragment : BaseFragment<FragmentEditPersonBinding>() {
         })
 
         viewModel.initPerson.observe(viewLifecycleOwner, Observer { result ->
-            if(result!=null){
+            if (result != null) {
                 when (result.status) {
                     Result.Status.LOADING -> {
                         binding?.progressBar?.visible(true)
@@ -127,17 +121,19 @@ class EditPersonFragment : BaseFragment<FragmentEditPersonBinding>() {
                         arguments?.remove("id")
                         binding?.progressBar?.visible(false)
                         binding?.etPersonName?.setText(result.data?.name)
-                        if (result.data?.gender==Gender.FEMALE)
-                            binding?.radioGroup?.check(R.id.rbFemale)
+                        if (result.data?.gender == Gender.FEMALE)
+                            binding?.genderSwitch?.setMale(false)
                         else
-                            binding?.radioGroup?.check(R.id.rbMale)
+                            binding?.genderSwitch?.setMale(true)
                         val calendar = Calendar.getInstance()
-                        calendar.time = result.data?.born?:Date()
-                        binding?.pickerBoD?.updateDate(calendar.get(Calendar.YEAR),
+                        calendar.time = result.data?.born ?: Date()
+                        binding?.pickerBoD?.updateDate(
+                            calendar.get(Calendar.YEAR),
                             calendar.get(Calendar.MONTH),
-                            calendar.get(Calendar.DAY_OF_MONTH))
+                            calendar.get(Calendar.DAY_OF_MONTH)
+                        )
                         viewModel.initDone()
-                        arguments?.putLong("current_id",result.data?.id?:-1)
+                        arguments?.putLong("current_id", result.data?.id ?: -1)
                         invalidateOptionsMenu(activity)
                     }
                     Result.Status.ERROR -> {
@@ -149,7 +145,7 @@ class EditPersonFragment : BaseFragment<FragmentEditPersonBinding>() {
         })
 
         viewModel.deletedPerson.observe(viewLifecycleOwner, Observer { result ->
-            if(result!=null){
+            if (result != null) {
                 when (result.status) {
                     Result.Status.LOADING -> {
                         binding?.progressBar?.visible(true)
@@ -167,7 +163,7 @@ class EditPersonFragment : BaseFragment<FragmentEditPersonBinding>() {
         })
 
         viewModel.sharePerson.observe(viewLifecycleOwner, Observer { result ->
-            if(result!=null){
+            if (result != null) {
                 when (result.status) {
                     Result.Status.LOADING -> {
                         binding?.progressBar?.visible(true)
@@ -195,7 +191,7 @@ class EditPersonFragment : BaseFragment<FragmentEditPersonBinding>() {
         }
         val deleteMenuItem = menu.findItem(R.id.deletePerson)
         val shareMenu = menu.findItem(R.id.sharePersonToUser)
-        val current_id = arguments?.getLong("current_id",-1)?:-1
+        val current_id = arguments?.getLong("current_id", -1)?:-1
         deleteMenuItem.isVisible = current_id>0
         shareMenu.isVisible = current_id>0
         return super.onCreateOptionsMenu(menu, inflater)
@@ -208,13 +204,12 @@ class EditPersonFragment : BaseFragment<FragmentEditPersonBinding>() {
             showError(getString(R.string.network_inactive))
             return false
         }
-        val current_id = arguments?.getLong("current_id",-1)?:-1
+        val current_id = arguments?.getLong("current_id", -1)?:-1
         when(item.itemId){
             R.id.addPerson -> {
-                if(current_id>0){
+                if (current_id > 0) {
                     viewModel.editPerson(current_id)
-                }
-                else {
+                } else {
                     viewModel.addPerson()
                 }
             }

+ 8 - 6
feature_person/src/main/java/com/mrozon/feature_person/presentation/EditPersonFragmentViewModel.kt

@@ -21,9 +21,11 @@ import javax.inject.Inject
 
 class EditPersonFragmentViewModel @Inject constructor(val context: Context, val repository: PersonRepository): BaseViewModel() {
 
-    private val _male = MutableLiveData<Boolean>(true)
-    val male: LiveData<Boolean>
-        get() = _male
+//    private val _male = MutableLiveData<Boolean>(true)
+//    val male: LiveData<Boolean>
+//        get() = _male
+
+    private var _male:Boolean = true
 
     private var _person = MutableLiveData<Result<Person>?>(null)
     val person: LiveData<Result<Person>?>
@@ -70,13 +72,13 @@ class EditPersonFragmentViewModel @Inject constructor(val context: Context, val
     }
 
     fun setMaleGender(isMale: Boolean) {
-        _male.value = isMale
+        _male = isMale
     }
 
     @ExperimentalCoroutinesApi
     fun addPerson() {
         var gender = Gender.MALE
-        if(_male.value==false)
+        if(_male==false)
             gender = Gender.FEMALE
         val personEntity = Person(name = personNameChannel.value, gender = gender, born = personDobChannel.value )
         viewModelScope.launch(Dispatchers.IO) {
@@ -115,7 +117,7 @@ class EditPersonFragmentViewModel @Inject constructor(val context: Context, val
     @ExperimentalCoroutinesApi
     fun editPerson(id: Long) {
         var gender = Gender.MALE
-        if(_male.value==false)
+        if(!_male)
             gender = Gender.FEMALE
         val personEntity = Person(id = id, name = personNameChannel.value, gender = gender, born = personDobChannel.value )
         viewModelScope.launch(Dispatchers.IO) {

+ 17 - 40
feature_person/src/main/res/layout/fragment_edit_person.xml

@@ -26,52 +26,17 @@
                 android:id="@+id/etPersonName"
                 android:layout_width="0dp"
                 android:layout_height="wrap_content"
-                android:layout_marginStart="8dp"
-                android:layout_marginEnd="16dp"
+                android:layout_marginStart="32dp"
+                android:layout_marginEnd="32dp"
                 android:ems="10"
                 android:hint="@string/hint_person_name"
                 android:imeOptions="actionDone"
                 android:inputType="textPersonName"
                 app:layout_constraintEnd_toEndOf="parent"
-                app:layout_constraintStart_toEndOf="@+id/ivGenger"
+                app:layout_constraintStart_toStartOf="parent"
                 app:layout_constraintTop_toTopOf="@id/guideline"
                 tools:text="Ivan Petrov" />
 
-            <ImageView
-                android:id="@+id/ivGenger"
-                android:layout_width="wrap_content"
-                android:layout_height="wrap_content"
-                android:layout_marginStart="16dp"
-                app:layout_constraintBottom_toBottomOf="@+id/etPersonName"
-                app:layout_constraintStart_toStartOf="parent"
-                app:layout_constraintTop_toTopOf="@+id/etPersonName"
-                app:srcCompat="@drawable/ic_male_avatar" />
-
-            <RadioGroup
-                android:id="@+id/radioGroup"
-                android:layout_width="wrap_content"
-                android:layout_height="wrap_content"
-                android:layout_marginTop="16dp"
-                android:checkedButton="@id/rbMale"
-                android:orientation="horizontal"
-                app:layout_constraintEnd_toEndOf="parent"
-                app:layout_constraintStart_toStartOf="parent"
-                app:layout_constraintTop_toBottomOf="@id/etPersonName">
-
-                <RadioButton
-                    android:id="@+id/rbMale"
-                    android:layout_width="match_parent"
-                    android:layout_height="wrap_content"
-                    android:text="@string/male" />
-
-                <RadioButton
-                    android:id="@+id/rbFemale"
-                    android:layout_width="match_parent"
-                    android:layout_height="wrap_content"
-                    android:text="@string/female" />
-
-            </RadioGroup>
-
             <DatePicker
                 android:id="@+id/pickerBoD"
                 android:layout_width="wrap_content"
@@ -88,8 +53,8 @@
                 android:layout_height="wrap_content"
                 android:layout_marginTop="16dp"
                 android:text="@string/tvDOB"
-                app:layout_constraintStart_toStartOf="@+id/etPersonName"
-                app:layout_constraintTop_toBottomOf="@id/radioGroup" />
+                app:layout_constraintStart_toStartOf="@+id/pickerBoD"
+                app:layout_constraintTop_toBottomOf="@+id/genderSwitch" />
 
             <ProgressBar
                 android:id="@+id/progressBar"
@@ -104,6 +69,18 @@
                 app:layout_constraintTop_toTopOf="parent"
                 tools:visibility="visible" />
 
+            <com.mrozon.feature_person.customview.GenderSwitch
+                android:id="@+id/genderSwitch"
+                android:layout_width="0dp"
+                android:layout_height="50dp"
+                android:layout_marginStart="24dp"
+                android:layout_marginTop="16dp"
+                android:layout_marginEnd="24dp"
+                android:padding="4dp"
+                app:layout_constraintEnd_toEndOf="@+id/pickerBoD"
+                app:layout_constraintStart_toStartOf="@+id/pickerBoD"
+                app:layout_constraintTop_toBottomOf="@+id/etPersonName" />
+
 
         </androidx.constraintlayout.widget.ConstraintLayout>
 

+ 7 - 0
feature_person/src/main/res/values/attrs.xml

@@ -0,0 +1,7 @@
+<resources>
+    <declare-styleable name="GenderSwitch">
+        <attr name="isMale" format="boolean" />
+        <attr name="colorChecked" format="color" />
+        <attr name="colorUnchecked" format="color" />
+    </declare-styleable>
+</resources>