How to access child view in layout in Custom View Group? - android

The picture below shows my custom view.
My Custom Layout Image
I want add custom buttons in child layout in my custom view.
So, I tried like below.
My CustomView is a ConstraintLayout and the code is
class MyCustomLayout(
context: Context,
attributeSet: AttributeSet? = null
) : ConstraintLayout(context, attributeSet) {
private var binding: MyCustomLayoutBinding
init {
binding = HarusekkiBaseToolbarBinding.inflate(
LayoutInflater.from(context),
this,
true
)
}
override fun addView(child: View?, index: Int, params: ViewGroup.LayoutParams?) {
child ?: return
if (child is HaruSekkiToolbarButton) {
binding.buttonContainer.addView(child, index, params)
} else {
super.addView(child, index, params)
}
}
}
Also, I wrote xml of fragment
<ConstraintLayout
.../>
<MyCustomLayout
...>
<MyCustomButton
android:id="#+id/myButton1
... />
<MyCustomButton
android:id="#+id/myButton2
... />
</MyCustomLayout>
</ConstraintLayout>
When I access MyCustomButton in Fragment Class like
binding.myButton1.do_somthing
it returns null.
How to I get MyCustomButton? Is addView method what I override wrong?
I've also changed the addView function in MyCustomLayout as follows.
private val childViews = mutableListOf<View>()
override fun addView(child: View?, index: Int, params: ViewGroup.LayoutParams?) {
child ?: return
childViews.add(child)
if (child is HaruSekkiToolbarButton) {
binding.toolbarButtonContainer.addView(child, index, params)
} else {
super.addView(child, index, params)
}
}
override fun getChildCount(): Int {
return childViews.size
}
override fun getChildAt(index: Int): View {
return childViews[index]
}
override fun indexOfChild(child: View?): Int {
return childViews.indexOf(child)
}
However, it didn't work well with ClassCastException.
(java.lang.ClassCastException: androidx.constraintlayout.widget.ConstraintLayout$LayoutParams cannot be cast to android.widget.LinearLayout$LayoutParams)
Plz Help me!

Related

MotionLayout not recyclable as a child of RecyclerView

i try to implement programmatically version of MotionLayout by extending it. And i have a base activity ayout using RecyclerView.
However, when i add my motion layout as an item of the RecyclerView, the view is not recycled when i try to scrolling up and down.
And it works well when i use as a normal view (act as single view).
Here is the preview:
class SimpleMotionLayout #JvmOverloads constructor(
context: Context,
attrs: AttributeSet? = null,
defStyleAttr: Int = 0
) : MotionLayout(context, attrs, defStyleAttr) {
private val motionScene = MotionScene(this)
private var _simpleTransition: MotionScene.Transition? = null
private lateinit var squareView: View
init {
layoutParams = LayoutParams(LayoutParams.MATCH_PARENT, LayoutParams.WRAP_CONTENT)
initDefaultConstraint(this)
setMotion()
}
fun setMotion() {
_simpleTransition = createPlaceholderTransition(motionScene)
setDebugMode(DEBUG_SHOW_PATH)
/**
* The order matters here.
* [MotionScene.addTransition] adds the transition to the scene while
* [MotionScene.setTransition] sets the transition to be the current transition.
*/
motionScene.addTransition(_simpleTransition)
motionScene.setTransition(_simpleTransition)
scene = motionScene
setTransition(_simpleTransition!!.id)
animateView()
}
fun setSquareColor(color: Int) {
squareView.setBackgroundColor(color)
}
fun initDefaultConstraint(motionLayout: ConstraintLayout) {
// View
squareView = View(context).apply {
id = R.id.default_button
setBackgroundColor(Color.BLACK)
}
motionLayout.addView(
squareView,
LayoutParams(
fromDp(context, 52),
fromDp(context, 52)
)
)
val set = ConstraintSet()
set.clone(motionLayout)
// Setup constraint set to TOP, LEFT to the Parent
set.connect(
squareView.id,
TOP,
PARENT_ID,
TOP
)
set.connect(
squareView.id,
START,
PARENT_ID,
START
)
set.applyTo(motionLayout)
}
private fun setToEnd() {
val endSet = getConstraintSet(_simpleTransition?.endConstraintSetId ?: return)
endSet.clear(R.id.default_button, START)
endSet.connect(
R.id.default_button,
END,
PARENT_ID,
END
)
}
fun animateView() {
setToEnd()
_simpleTransition?.setOnSwipe(
OnSwipe().apply {
dragDirection = DRAG_END
touchAnchorId = R.id.default_button
touchAnchorSide = SIDE_START
onTouchUp = ON_UP_AUTOCOMPLETE_TO_START
setMaxAcceleration(500)
}
)
setTransition(_simpleTransition!!.id)
}
// Placeholder transition??
fun createPlaceholderTransition(motionScene: MotionScene): MotionScene.Transition? {
val startSetId = View.generateViewId()
val startSet = ConstraintSet()
startSet.clone(this)
val endSetId = View.generateViewId()
val endSet = ConstraintSet()
endSet.clone(this)
val transitionId = View.generateViewId()
return TransitionBuilder.buildTransition(
motionScene,
transitionId,
startSetId, startSet,
endSetId, endSet
)
}
/**
* Get px from dp
*/
private fun fromDp(context: Context, inDp: Int): Int {
val scale = context.resources.displayMetrics.density
return (inDp * scale).toInt()
}
}
Below is my adapter:
class SimpleMotionLayoutAdapter : RecyclerView.Adapter<SimpleMotionLayoutAdapter.ViewHolder>() {
val items = mutableListOf<Int>() // colors
class ViewHolder(val view: View) : RecyclerView.ViewHolder(view) {
fun setColor(color: Int) {
(view as SimpleMotionLayout).setSquareColor(color)
}
}
override fun getItemId(position: Int): Long {
return position.toLong()
}
override fun onCreateViewHolder(parent: ViewGroup, viewType: Int): ViewHolder {
val view = SimpleMotionLayout(parent.context)
return ViewHolder(view)
}
override fun onBindViewHolder(holder: ViewHolder, position: Int) {
holder.setColor(items[position])
}
override fun getItemCount(): Int = items.size
companion object {
const val TYPE_NORMAL = 0
const val TYPE_EXCEPTIONAL = 1
}
}
Am i missing implementation?
Thank you
In general you need to cache and restore the state of the MotionLayout when it gets Recycled.
Right now in onBindViewHolder you only set the Color.
Remember RecyclerView keeps a only a screens worth (+ about 3) of ViewHolders and reuses them using onBindViewHolder
At minimum you need to set the Progress of the MotionLayout.
Due to differences in timing you may need to set the progress in an onPost

Does the recyclerview/listview adapter have a method called once?

For a certain reason, I have to do one operation inside the adapter. In other words, this process should not be repeated when scrolling. The onCreate method in the activity works once when the activity is opened. Is there a method to do this inside the adapter?
UPDATE
My adapter class:
class EducationAdapter #Inject constructor(
var userManager: UserManager
) : ListAdapter<EducationModel, RecyclerView.ViewHolder>.
(EDUCATION_ITEM_DIFF_CALLBACK) {
companion object {
}
var callBack: EducationAdapterCallBack? = null
interface EducationAdapterCallBack {
fun onClickLayer(educationModel: EducationModel)
}
class EducationViewHolder(var binding: ItemEducationBinding) :
RecyclerView.ViewHolder(binding.root) {
fun setItem(item: EducationModel) {
binding.educationItem = item
}
}
#Nonnull
override fun onCreateViewHolder(
#Nonnull parent: ViewGroup,
viewType: Int
): RecyclerView.ViewHolder {
val binding: ItemEducationBinding = DataBindingUtil.inflate(
LayoutInflater.from(parent.context),
R.layout.item_education, parent, false
)
return EducationAdapter.EducationViewHolder(binding)
}
override fun onBindViewHolder(#Nonnull holder: RecyclerView.ViewHolder, position: Int) {
val item = getItem(position)
when (holder) {
is EducationAdapter.EducationViewHolder -> {
holder.setItem(item)
holder.binding.itemEducation.setOnClickListener {
callBack?.onClickLayer(item)
}
}
}
}
}
OncreateViewHolder and onBindViewHolder called when scrolling listview. But I want a single shot process is there any method that runs a single time?
UPDATE 2
I created a dummy operation (My original code was too long, I created summarized dummy operation). OnBind method should be like this:
override fun onBindViewHolder(#Nonnull holder: RecyclerView.ViewHolder, position: Int) {
val item = getItem(position)
when (holder) {
is EducationAdapter.EducationViewHolder -> {
holder.setItem(item)
holder.binding.itemEducation.setOnClickListener {
callBack?.onClickLayer(item)
}
var isExpandClicked = false
holder.binding.btnExpandProject.setOnClickListener {
if (isExpandClicked) {
isExpandClicked = false
val context = holder.binding.llTaskFaaliyet.context
holder.binding.btnExpandProject.setImageDrawable(
ContextCompat.getDrawable(
context,
R.drawable.ic_arrow_down
)
)
val headerText = TextView(context)
headerText.apply {
text = subTask.title
textSize = 16f
setTextColor(ContextCompat.getColor(context, R.color.marine))
gravity = Gravity.START
typeface = ResourcesCompat.getFont(context, R.font.ubuntu_bold)
setPadding(48, 16, 4, 0)
setBackgroundColor(ContextCompat.getColor(context, R.color.white))
}
holder.binding.llTask.add(headerText)
}else{
isExpandClicked = true
val context = holder.binding.llTask.context
holder.binding.btnExpandProject.setImageDrawable(
ContextCompat.getDrawable(
context,
R.drawable.ic_close
)
)
holder.binding.llTask.removeAllViews()
}
}
}
}
}
I am trying to create those views below:
Expanded View:
Shrinked View
Views should be expanded at the first opening. It should be single time when the view is created.
If you put the function on bind method, it should be triggered whenever the item is shown. You can make a simple way, use boolean on adapter scope that will be changed to false after the expand function is triggered. Then use this boolean to check whether value is true, so it should trigger the expand function.

IllegalStateException: must call removeView() on the child's parent first when Using GridView and ViewPager

I am using ViewPager with GridView and databinding.
The code is like the following:
private fun initGridView(){
//page count
val totalPageSize = ceil(viewModel.rechargePaymentList.value!!.size.toFloat() / 6).toInt()
val viewPagerList = ArrayList<GridView>()
val layoutInflater = LayoutInflater.from(context)
//GridView
for (page in 0 until totalPageSize){
layoutInflater.inflate(R.layout.layout_gridview,binding.viewPager,false).apply {
val gridView = findViewById<GridView>(R.id.gridView)
gridView.adapter = GridViewAdapter(viewModel,context!!,page)
viewPagerList.add(gridView)
}
}
binding.viewPager.adapter = RechargeWayViewPagerAdapter(viewPagerList)
}
Adapter is like the following
class RechargeWayViewPagerAdapter(private val viewList:ArrayList<GridView>): PagerAdapter() {
override fun instantiateItem(container: ViewGroup, position: Int): GridView {
container.addView(viewList[position])
return viewList[position]
}
override fun isViewFromObject(view: View, any: Any): Boolean {
return view == any as GridView
}
override fun getCount(): Int {
return viewList.size
}
override fun destroyItem(container: ViewGroup, position: Int, any: Any) {
container.removeView(viewList[position])
}
}
But it show
IllegalStateException: The specified child already has a parent. You must call removeView() on the child's parent first
I have try container.removeView(viewList[position]) in override fun instantiateItem.
But it seems not working.
Did I missing something ?
Thanks in advance.
From the issue, your childView already has a parent so do something as below.
Assuming that container.addView(viewList[position]) line is culprit then childview will be View childView = viewList[position]
if (childView != null) {
ViewGroup parent = (ViewGroup) childView.getParent();
if (parent != null) {
parent.removeView(childView);
}
}

How to implement custom ViewGroup in RecyclerView Adapter

I am using a CarouselView library to show images in a RecyclerView.
I have created a custom view which uses this PhotoView library, to make the images zoomable.
I need to implement a custom ViewGoup class which is explained here. I don't know where to change my RecyclerView Adapter, to use my CustomViewGroup instead of the default ViewGroup.
My ViewGroup class looks like shown
class MyViewGroup(context: Context) : ViewGroup(context) {
override fun onLayout(changed: Boolean, l: Int, t: Int, r: Int, b: Int) {}
override fun onInterceptTouchEvent(ev: MotionEvent): Boolean {
try {
return super.onInterceptTouchEvent(ev)
} catch (e: IllegalArgumentException) {
e.printStackTrace()
return false
}
}
}
And this is my recyclerview adapter
class MyAdapter(
var dataset: MutableList<Upload> = mutableListOf(),
var context: Context?
) : RecyclerView.Adapter<MyAdapter.ViewHolder>() {
class ViewHolder(val layout: RelativeLayout) : RecyclerView.ViewHolder(layout)
override fun onCreateViewHolder(parent: ViewGroup, viewType: Int): ViewHolder {
val cont = LayoutInflater.from(parent.context)
.inflate(R.layout.my_item, parent, false) as RelativeLayout
return ViewHolder(cont)
}
override fun onBindViewHolder(holder: ViewHolder, position: Int) {}
override fun getItemCount(): Int {
return dataset.size
}
}

Why only first row is showing using Recyclerview gridlayoutmanager

Adapter Calling:
adherenceDetails.hasFixedSize()
adherenceDetails.layoutManager= GridLayoutManager(this#PatientPlanStatus,3)
adherenceDetails.adapter= PatientPlanDetails(this#PatientPlanStatus, patientPlanAdherenceData.data?.get(0)?.adherence!!)
Adapter:
class PatientPlanDetails(var mcontext: Context, var patientDatas:ArrayList<AdherencePoJo>): RecyclerView.Adapter<RecyclerView.ViewHolder>() {
override fun getItemCount(): Int {
return patientDatas.size
}
val TAG="PatientPlanDetails"
override fun onCreateViewHolder(parent: ViewGroup?, viewType: Int): RecyclerView.ViewHolder {
val v = LayoutInflater.from(mcontext).inflate(R.layout.patient_plan_adherence, parent, false)
return Item(v)
}
override fun onBindViewHolder(holder: RecyclerView.ViewHolder?, position: Int) {
//Log.i(TAG,"On Bind")
(holder as Item).bindData(patientDatas.get(position),mcontext)
}
class Item(itemView: View) : RecyclerView.ViewHolder(itemView) {
fun bindData(patienData: AdherencePoJo, context: Context) {
val TAG="PatientPlanDetails"
}
}
}
getItemCount() returning the correct size. But only first row is showing. Please check the image
Card has actual height but the item inside it are not visible.
I am getting no log in the logout also.
Make your patient_plan_adherence layout height as wrap_content
Try with this property in your RecyclerView:
app:layout_behavior="#string/appbar_scrolling_view_behavior"

Categories

Resources