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).
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.
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.
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.
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.