Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Android: Multiple view children for custom view with existing layout

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! :)

like image 852
mreichelt Avatar asked Jun 25 '12 22:06

mreichelt


People also ask

How can use multiple layouts in one activity in Android?

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.

Can a view contain other views?

A ViewGroup is a special view that can contain other views (called children.)

Is it possible to include one layout definition in another?

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.

When creating a custom view in Android which method is responsible for displaying that view on the screen?

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.


1 Answers

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.


Kotlin version:

     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() 
like image 172
devunwired Avatar answered Sep 23 '22 21:09

devunwired