Expanding parent's height when expanding's child height - android

I am using this ExpandableView component https://github.com/nicolasjafelle/ExpandableView
which has a main class: https://github.com/nicolasjafelle/ExpandableView/blob/master/ExpandableViewProject/expandableview/src/main/java/com/expandable/view/ExpandableView.java
The component wraps other custom components specified by the user. When the custom components have fixed height, everything works fine.
However, I am creating a custom component which has a dynamic number of rows. Suppose this is the initial state:
this is row1
this is row2
this is row3
button
when I click the button, it will show
this is row1
this is row2
this is row3
this is row4
button
When I test my component in a LinearLayout, there is not problem and the new rows are shown. However, when I embed my custom component in ExpandableView, the ContentView of ExpandableView does not extend to wrap the new rows but instead, the button is hidden at the bottom.
I guess that to allow ExpandableView's ContentView to dynamically extend according to the size of the inner custom component I should implement onMeasure() method of ExpandableView. But so far, all my tests have being failed.
The hierarchy of my custom component is:
top
...
ExpandableView (extension of RelativeLayout, no onMeasure method)
LinearLayout (defined in XML)
ExpandableView (extension of RelativeLayout, no onMeasure method)
My custom component (WeekTimes, extension of LinearLayout, no onMeasure method)
Any idea?
I guess this is a bug in ExpandableView component but do not know how to fix it.
Further detail: this is the code for instantiating ExpandableView:
ExpandableView expandableContract = (ExpandableView) formsContained.findViewById(R.id.expandable_academy);
expandableContract.fillData(R.drawable.button_selected_full, getStringResource(R.string.about_academy), false);
expandableContract.addContentView(getLayoutInflater().inflate(R.layout.activity_offer_form_academy, new ExpandableView(this), true));
where R.layout.activity_offer_form_academy is just a layout with my component:
<com.dynassets.assets.util.weektimes.WeekTimes
xmlns:android="http://schemas.android.com/apk/res/android"
android:id="#+id/weektimes"
android:layout_width="match_parent"
android:layout_height="wrap_content"/>
and the code of my custom component is
public class WeekTimes extends LinearLayout {
public WeekTimes(Context context) {
super(context);
init();
}
public WeekTimes(Context context, AttributeSet attrs) {
super(context, attrs);
init();
}
private void init() {
setLayoutParams(new LayoutParams(LayoutParams.MATCH_PARENT,LayoutParams.WRAP_CONTENT));
setOrientation(LinearLayout.VERTICAL);
Button button = new Button(getContext());
button.setText("click me");
addView(button);
button.setOnClickListener(new OnClickListener() {
#Override
public void onClick(View view) {
try {
Button button = new Button(getContext());
button.setText("click me2");
addView(button);
requestLayout();
} catch (Exception e) {
Log.e("MyApp", "exception", e);
}
}
});
}
}
So, how to tell the parent ExpandableView to extend according to the children's height? Is this a matter of creating a onMeasure() method?

Well, the problem was that ExpandableView contained an animation and this animation was fixing the height preventing the android layouts work as expected. Here is described in further detail:
https://github.com/nicolasjafelle/ExpandableView/issues/5
So the fix is reassigning the height to a relative value after the animation has ended. Issue is fixed by adding these new lines on the ExpandableView extension on the animation method:
animator.addListener(new AnimatorListenerAdapter() {
#Override
public void onAnimationEnd(Animator animation) {
// release height
ViewGroup.LayoutParams layoutParams = contentLayout.getLayoutParams();
layoutParams.height = ViewGroup.LayoutParams.WRAP_CONTENT;
contentLayout.setLayoutParams(layoutParams);
}
});

Related

How to ignore padding of parent view?

I have ScrollView layout (or other layout type it doesn't mater) which has child views. And this parent layout has paddingLeft and paddingRight. I want to have this padding set for each child, but sometimes I have exception where I want that child to reach edges of the display completely (for example TextView with background color). Is there any way how to allow this to happen? I don't wanna set padding for every single child separately.
You told us what you want: "sometimes a Child shouldn't have to respect its Parent padding", so the special behaviour should occurs on Children.
The solution is to EXTEND Child's main View in which it chooses its MARGIN by default and then create a "Child.setNoMargins()" method that removes them from itself when required.
public static class ExtendedTextView extends TextView {
private boolean mHasNoMargins = false;
public ExtendedTextView(Context context) {
super(context);
}
public void setNoMargins() {
mHasNoMargins = true;
if (!isInLayout() && isAttachedToWindow()) requestLayout();
}
#Override
public void setLayoutParams(ViewGroup.LayoutParams params) {
if (!mHasNoMargins && (params instanceof ViewGroup.MarginLayoutParams)) {
((ViewGroup.MarginLayoutParams)params).setMarginStart(20);
((ViewGroup.MarginLayoutParams)params).setMarginEnd(20);
}
super.setLayoutParams(params);
}
}
In this way all created "ExtendedTextView" have default Left/Right margins until you call "setNoMargins()".
Obliviously my code works only if parent ViewGroup supports LayoutParams having Margins (most of them).

Create Android subviews like iOS subviews

I'm trying to programmatically (not using XML files) create custom subviews in Android (that's what I call it in iOS) that is a basically a number of basic views (labels, buttons, text fields etc) put together into a reusable subview class so I can use it inside my UIViewControllers or Activity in Android.
I don't know what is the correct terminology in Android. There seems to be a million different terminologies.
Custom View, ViewGroups, Layouts, Widgets, Components, whatever you want to call it.
In iOS this is simply done like this:
CustomView.h
#interface CustomView : UIView
#property (nonatomic, strong) UILabel *message;
#property (nonatomic, strong) UIButton *button;
#end
CustomView.m
#implementation CustomView
-(id)initWithFrame:(CGRect)frame
{
self = [super initWithFrame:frame];
if(self)
{
[self initViews];
[self initConstraints];
}
return self;
}
-(void)initViews
{
self.message = [[UILabel alloc] init];
self.button = [[UIButton alloc] init];
[self addSubview:self.message];
[self addSubview:self.button];
}
-(void)initConstraints
{
id views = #{
#"message": self.message,
#"button": self.button
};
[self addConstraints:[NSLayoutConstraint constraintsWithVisualFormat:#"H:|[message]|" options:0 metrics:nil views:views]];
[self addConstraints:[NSLayoutConstraint constraintsWithVisualFormat:#"H:|[button]|" options:0 metrics:nil views:views]];
[self addConstraints:[NSLayoutConstraint constraintsWithVisualFormat:#"V:|[message][button]|" options:0 metrics:nil views:views]];
}
#end
Now I can reuse this custom view in any ViewController (Android Activity) I chose.
How does one achieve something like that in Android?
I've been looking around and from what I gather in Android, to add subviews, I add them to Layouts:
RelativeLayout relativeLayout = new RelativeLayout(...);
TextView textView = new TextView(...);
relativeLayout.addSubview(textView);
Does that mean I need extend RelativeLayout or ViewGroup?
Looking at this page: http://developer.android.com/reference/android/view/ViewGroup.html
It seems like we need to write some really complicated logic to layout the custom view such as:
#Override
protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
int count = getChildCount();
// These keep track of the space we are using on the left and right for
// views positioned there; we need member variables so we can also use
// these for layout later.
mLeftWidth = 0;
mRightWidth = 0;
// Measurement will ultimately be computing these values.
int maxHeight = 0;
int maxWidth = 0;
int childState = 0;
// Iterate through all children, measuring them and computing our dimensions
// from their size.
for (int i = 0; i < count; i++) {
final View child = getChildAt(i);
if (child.getVisibility() != GONE) {
// Measure the child.
measureChildWithMargins(child, widthMeasureSpec, 0, heightMeasureSpec, 0);
// Update our size information based on the layout params. Children
// that asked to be positioned on the left or right go in those gutters.
final LayoutParams lp = (LayoutParams) child.getLayoutParams();
if (lp.position == LayoutParams.POSITION_LEFT) {
mLeftWidth += Math.max(maxWidth,
child.getMeasuredWidth() + lp.leftMargin + lp.rightMargin);
} else if (lp.position == LayoutParams.POSITION_RIGHT) {
mRightWidth += Math.max(maxWidth,
child.getMeasuredWidth() + lp.leftMargin + lp.rightMargin);
} else {
maxWidth = Math.max(maxWidth,
child.getMeasuredWidth() + lp.leftMargin + lp.rightMargin);
}
maxHeight = Math.max(maxHeight,
child.getMeasuredHeight() + lp.topMargin + lp.bottomMargin);
childState = combineMeasuredStates(childState, child.getMeasuredState());
}
}
// Total width is the maximum width of all inner children plus the gutters.
maxWidth += mLeftWidth + mRightWidth;
// Check against our minimum height and width
maxHeight = Math.max(maxHeight, getSuggestedMinimumHeight());
maxWidth = Math.max(maxWidth, getSuggestedMinimumWidth());
// Report our final dimensions.
setMeasuredDimension(resolveSizeAndState(maxWidth, widthMeasureSpec, childState),
resolveSizeAndState(maxHeight, heightMeasureSpec,
childState << MEASURED_HEIGHT_STATE_SHIFT));
}
All I'm trying to do is use multiple basic android labels, views, buttons in a custom view like the iOS example above, why is it so hard in Android ?
I was hoping for something simple like this:
public class CustomView extends View
{
public RelativeLayout mainLayout;
public TextView message;
public Button button;
// default constructor
public CustomView()
{
...
initViews();
initLayouts();
addViews();
}
public initViews()
{
mainLayout = new RelativeLayout(this);
message = new TextView(this);
button = new Button(this);
...
}
public initLayouts()
{
// --------------------------------------------------
// use Android layout params to position subviews
// within this custom view class
// --------------------------------------------------
}
public addViews()
{
mainLayout.addView(message);
mainLayout.addView(button);
setContentView(mainLayout);
}
}
I'm sorry I am sincerely trying to learn and build a basic Android application and not trying to bash Android's way of doing things.
I know how to add and layout subviews inside an Activity and have been doing so for the past two days but not inside a custom View/View Group/Layout. I don't want to end up constructing the exact same subview for each of my Activity in the Android app, that just goes against good coding practice right ? :D
Just need a bit of guidance here from others who have done both iOS and Android development.
Edit
It seems like what I'm looking for is called a Compound Control: http://developer.android.com/guide/topics/ui/custom-components.html
I'll keep digging and hopefully achieve the result I'm after :D
Just need to work out this Inflater business.
OK, I think I got it, not sure if it's the best solution but it does what I want.
So it goes something like this:
public class CustomView extends RelativeLayout
{
private Context context;
public TextView message;
public Button button;
public CustomView(Context context)
{
super(context);
// ---------------------------------------------------------
// store context as I like to create the views inside
// initViews() method rather than in the constructor
// ---------------------------------------------------------
this.context = context;
initViews();
initLayouts();
addViews();
}
public CustomView(Context context, AttributeSet attrs)
{
super(context, attrs);
// ---------------------------------------------------------
// store context as I like to create the views inside
// initViews() method rather than in the constructor
// ---------------------------------------------------------
this.context = context;
initViews();
initLayouts();
addViews();
}
public initViews()
{
// ----------------------------------------
// note "context" refers to this.context
// that we stored above.
// ----------------------------------------
message = new TextView(context);
...
button = new Button(context);
...
}
public initLayouts()
{
// --------------------------------------------------
// use Android layout params to position subviews
// within this custom view class
// --------------------------------------------------
message.setId(View.generateViewId());
button.setId(View.generateViewId());
RelativeLayout.LayoutParams messageLayoutParams = new RelativeLayout.LayoutParams(
LayoutParams.MATCH_PARENT,
LayoutParams.MATCH_PARENT
);
message.setLayoutParams(messageLayoutParams);
RelativeLayout.LayoutParams buttonLayoutParams = new RelativeLayout.LayoutParams(
LayoutParams.MATCH_PARENT,
LayoutParams.WRAP_CONTENT
);
button.setLayoutParams(buttonLayoutParams);
}
public addViews()
{
// adding subviews to layout
addView(message);
addView(button);
}
}
Now I can use this custom view in any of my Activity:
public class MainActivity extends ActionBarActivity {
// custom view instance
protected CustomView approvalView;
#Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
...
initViews();
}
public initViews()
{
...
approvalView = new CustomView(this);
approvalView.message.setText("1 + 1 = 2");
approvalView.button.setOnClickListener(new View.OnClickListener() {
#Override
public void onClick(View view) {
Log.d("Logger", "Math formula approved! :D");
}
});
}
}
Inflater is used if we create our layout using XML which isn't something I like to do, so I generated my view's layout programmatically :D
The above "RelativeLayout" in "extends RelativeLayout" can be replace with "LinearLayout" or other layouts of course.
To add a simple answer for the general visitor to this question...
You can't add subviews to an Android View like you can with an iOS UIView.
If you need to add subviews in Android, then use one of the ViewGroup subclasses (like LinearLayout, RelativeLayout, or even your own custom subclass).
myViewGroup.addView(myView);
Android and ios app development are two different concepts, each have its own way to perform your task. Sometimes its difficult to develop a piece of code in android and sometimes in ios.
To create your view screen/design/GUI in android you can create XML file (recommended) or by code (which is somehow difficult to maintain w.r.t XML).
For your question you don't need to create your own ViewGroup or RelativeLayout or LinearLayout e.g. if you want to use RelativeLayout as a parent for your view than by using XML you can use.
<RelativeLayout xmlns:android="http://schemas.android.com/apk/res/android"
android:layout_width="match_parent"
android:layout_height="match_parent">
<TextView
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="Some Text"/>
</RelativeLayout>
If you want to create your view pragmatically than use
RelativeLayout parentRelativeLayout = new RelativeLayout(context);
RelativeLayout.LayoutParams parentParams = new RelativeLayout.LayoutParams(LayoutParams.MATCH_PARENT, LayoutParams.MATCH_PARENT);
parentRelativeLayout.setLayoutParams(parentParams);
TextView childTextView = new TextView(context);
childTextView.setText("Some Text");
mRelativeLayout.addView(childTextView);
Its just a sample code both have identical output but with programmatic approach it will be difficult as your view grows.
Looking your code you are creating custom view (why?) in android we only need custom views if default views not are providing some functionally which we need to use/implement in our code.
As far as i understand you want to use custom views for reuse. Its good approach but if android is providing some functionality than why you are trying to invent wheel again, just use different layouts, use only custom views if you want something extra.

Inflate() ignores XML params and setLayoutParams() break setOnClickListener(this)

Here is a custom view:
public class SelectFrame extends FrameLayout implements FrameLayout.OnClickListener{
public SelectFrame(Context context) {
super(context);
init();
}
private void init() {
inflate(getContext(), R.layout.photo_selectable, this);
this.setOnClickListener(this);
setLayoutParams(
new TableRow.LayoutParams(
TableRow.LayoutParams.FILL_PARENT,
TableRow.LayoutParams.FILL_PARENT)
);
}
#Override
public void onClick(View v) {
Log.i("test", "yee!!");
}
}
The problem: R.layout.photo_selectable has in its xml in the outer view:
android:layout_width="fill_parent"
android:layout_height="fill_parent"
However these are getting ignored when I use inflate(getContext(), R.layout.photo_selectable, this);.
So that makes me set the parameters programmatically:
setLayoutParams(
new TableRow.LayoutParams(
TableRow.LayoutParams.FILL_PARENT,
TableRow.LayoutParams.FILL_PARENT)
);
I use TableRow.LayoutParams since TableRow would be the parent view. Now the layouts are correctly drawn but this leads to break the this.setOnClickListener(this); (the onclick() doesn't fire).
How can I have the custom SelectFrame view be filled in the parent but also have the OnClickListener work?
You seem somewhat confused. The layout parameters specified in XML are applied to the views specified in XML. They don't apply to the SelectFrame that hosts the views specified in XML.
As for your onClick not being called, most likely one of your child views is handling the event before you see it.

Adding form components to a custom android view

I have a custom view (extends View) and I would like to add controls (buttons/text boxes etc) in the OnCreate function to add these components to the view at runtime:
public Section(Context context) {
super(context);
this.setBackgroundColor(Color.argb(255, 242, 242, 242));
this.setOnTouchListener(mSectionOnTouch);
LinearLayout l = new LinearLayout(this.getContext());
l.setOrientation(LinearLayout.VERTICAL);
Button btn = new Button(this.getContext());
btn.setId(1);
btn.setText("btn1");
l.addView(btn);
Button btn2 = new Button(this.getContext());
btn2.setId(2);
btn2.setText("btn2");
l.addView(btn2);
} // Section
but this does not seem to do anything... Could someone tell me what im doing wrong?
Many thanks
FR
You never add l to your view. It should look like this:
public Section(Context context)
{
// setup linear layout
addView(l);
}
A slightly simpler way to do this would be to have your custom view extend LinearLayout. Then you can just add the views directly to your custom view and you don't have to nest another container, which is better for performance.

How do I position ZoomButtonsController in custom ImageView?

Layouting in Android is getting me rather perplexed.
I'm slowly implementing a custom ImageView where I'd like to make use of the ZoomButtonsController.
However, I would like to decide where the zoom buttons go in the layout and I can't figure out how to move them from the default bottom center position.
I have been experimenting with layouting simple views such as buttons in the main activity and this seems to be working as I would guess and expect.
In the case of the ZoomButtonsController I would however like to reposition them. I'm using a RelativeLayout as the mail layout and add the ZoomButtonsController within the custom ImageView.
The Activity code
public class ImageViewActivity extends Activity {
/** Called when the activity is first created. */
#Override
public void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.main);
CustomImageView imageView = new CustomImageView(this);
relativeLayout.addView(imageView);
}
}
The CustomImageView code
public class CustomImageView extends ImageView {
private ZoomButtonsController mZoomButtons;
public CustomImageView(Context context) {
super(context);
RelativeLayout.LayoutParams layoutParams = new RelativeLayout.LayoutParams(RelativeLayout.LayoutParams.FILL_PARENT, RelativeLayout.LayoutParams.FILL_PARENT);
mZoomButtons = new ZoomButtonsController(this);
mZoomButtons.getZoomControls();
layoutParams.addRule(RelativeLayout.ALIGN_PARENT_TOP);
layoutParams.addRule(RelativeLayout.CENTER_HORIZONTAL);
setLayoutParams(layoutParams);
}
#Override
public boolean onTouchEvent(MotionEvent ev) {
Log.d("TAG", "touch");
mZoomButtons.setVisible(true);
return true;
}
}
I've tested with WRAP_CONTENT in the parameters, but this only makes the zoom buttons disappear.
As a matter of fact, I couldn't position the ZoomButtonsController in any way and in the end had to accept the default placement.

Categories

Resources