I have to build a more complex custom view in Android. The final layout should look like this:
<RelativeLayout> <SomeView /> <SomeOtherView /> <!-- maybe more layout stuff here later --> <LinearLayout> <!-- the children --> </LinearLayout> </RelativeLayout>
However, in the XML files I just want do define this (without defining SomeView, SomeOtherView etc.):
<MyCustomView> <!-- the children --> </MyCustomView>
Is this possible in Android, and if yes: What would be the cleanest way to do it? The possible solutions that came to my mind were 'override the addView() methods' and 'remove all views and add them again later', but I am unsure which way to go...
Thanks a lot in advance for your help! :)
You can use as many layouts as possible for a single activity but obviously not simultaneously. You can use something like: if (Case_A) setContentView(R. layout.
A ViewGroup is a special view that can contain other views (called children.)
To efficiently re-use complete layouts, you can use the <include/> and <merge/> tags to embed another layout inside the current layout. Reusing layouts is particularly powerful as it allows you to create reusable complex layouts. For example, a yes/no button panel, or custom progress bar with description text.
Creating custom views. By extending the View class or one of its subclasses you can create your custom view. For drawing view use the onDraw() method. In this method you receive a Canvas object which allows you to perform drawing operations on it, e.g. draw lines, circle, text or bitmaps.
It's absolutely possible, and encouraged, to create custom container views. This is what Android would call a compound control. So:
public class MyCustomView extends RelativeLayout { private LinearLayout mContentView; public MyCustomView(Context context) { this(context, null); } public MyCustomView(Context context, AttributeSet attrs) { this(context, attrs, 0); } public MyCustomView(Context context, AttributeSet attrs, int defStyle) { super(context, attrs, defStyle); //Inflate and attach your child XML LayoutInflater.from(context).inflate(R.layout.custom_layout, this); //Get a reference to the layout where you want children to be placed mContentView = (LinearLayout) findViewById(R.id.content); //Do any more custom init you would like to access children and do setup } @Override public void addView(View child, int index, ViewGroup.LayoutParams params) { if(mContentView == null){ super.addView(child, index, params); } else { //Forward these calls to the content view mContentView.addView(child, index, params); } } }
You can override as many versions of addView()
as you feel are necessary, but in the end they all call back to the version I placed in the sample. Overriding just this method will have the framework pass all children found inside its XML tag to a specific child container.
And then modify the XML as such:
res/layout/custom_layout.xml
<merge> <SomeView /> <SomeOtherView /> <!-- maybe more layout stuff here later --> <LinearLayout android:id="@+id/content" /> </merge>
The reason for using <merge>
is to simplify the hierarchy. All the child views will get attached to your custom class, which is a RelativeLayout
. If you don't use <merge>
, you end up with a RelativeLayout
attached to another RelativeLayout
attached to all the children, which can cause issues.
private fun expand(view: View) { val parentWidth = (view.parent as View).width val matchParentMeasureSpec = MeasureSpec.makeMeasureSpec(parentWidth, MeasureSpec.EXACTLY) val wrapContentMeasureSpec = MeasureSpec.makeMeasureSpec(0, MeasureSpec.UNSPECIFIED) view.measure(matchParentMeasureSpec, wrapContentMeasureSpec) val targetHeight = view.measuredHeight view.isVisible = true val animation: Animation = getExpandAnimation(view, targetHeight) view.startAnimation(animation) } private fun getExpandAnimation( view: View, targetHeight: Int ): Animation = object : Animation() { override fun applyTransformation( interpolatedTime: Float, transformation: Transformation ) { view.layoutParams.height = if (interpolatedTime == 1f) { LayoutParams.WRAP_CONTENT } else { (targetHeight * interpolatedTime).toInt() } view.requestLayout() } override fun willChangeBounds(): Boolean { return true } }.apply { duration = getDuration(targetHeight, view) } private fun collapse(view: View) { val initialHeight = view.measuredHeight val animation: Animation = getCollapseAnimation(view, initialHeight) view.startAnimation(animation) } private fun getCollapseAnimation( view: View, initialHeight: Int ): Animation = object : Animation() { override fun applyTransformation( interpolatedTime: Float, transformation: Transformation ) { if (interpolatedTime == 1f) { view.isVisible = false } else { view.layoutParams.height = initialHeight - (initialHeight * interpolatedTime).toInt() view.requestLayout() } } override fun willChangeBounds(): Boolean = true }.apply { duration = getDuration(initialHeight, view) } /** * Speed = 1dp/ms */ private fun getDuration(initialHeight: Int, view: View) = (initialHeight / view.context.resources.displayMetrics.density).toLong()
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With