I'm trying to inflate xml-layout into custom View-class, which is then placed into LinearLayout in actual software. After some Googling I managed to create custom class which inflates the layout by using following class:
public class LitteringView extends RelativeLayout
{
public LitteringView(Context context) {
super(context);
// TODO Auto-generated constructor stub
View.inflate(context, R.layout.littering_layout, this);
}
public LitteringView(Context context, AttributeSet attrs) {
super(context, attrs);
// TODO Auto-generated constructor stub
}
#Override
protected void onLayout(boolean changed, int l, int t, int r, int b) {
// TODO Auto-generated method stub
for(int i = 0 ; i < getChildCount() ; i++){
getChildAt(i).layout(l, t, r, b);
}
}
#Override
protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
// TODO Auto-generated method stub
super.onMeasure(widthMeasureSpec, heightMeasureSpec);
}
}
Now it definitely inflates when I add it to the linearlayout:
layout.addView(new LitteringView(getActivity()));
Problem is that it takes the whole screen while the screen should be divided equally between LinearLayout's three children. Other Views are also created dynamically. How I can prevent it from taking the whole space?
I'm trying to create one custom View/class and package its functions inside it which I then could easily add to the parent layout (in this case the LinearLayout) while wanting to utilize xml to define the layout of the View.
You need to use 'weight' for your views. A simple example for this:
Layout.LayoutParams params = new LinearLayout.LayoutParams(width, height, weight);
You need to optimize it for your case. Give all your views a weight value equally(eg: 1 for all of them) and they should share the view.
Related
I'm extending a HorizontalScrollView. This layout will add children to a LinearLayout when the public setItems method is called. These children views are dynamically inflated and their widths depend on the parent (fill parent when only one view, and 1/2 parent when >= 2 items).
<!-- custom_layout.xml -->
<?xml version="1.0" encoding="utf-8"?>
<merge xmlns:android="http://schemas.android.com/apk/res/android"
android:layout_width="match_parent"
android:layout_height="match_parent">
<LinearLayout
android:id="#+id/container"
android:orientation="horizontal"
android:layout_width="wrap_content"
android:layout_height="match_parent">
</LinearLayout>
</merge>
Sometimes, setItems is called in a callback of a network request, so the layout might already be fully inflated. Where should I be calling updateView which needs the parent's width and to add inflated children? I put the call in both onSizeChanged and setItems like below.
public class CustomLayout extends HorizontalScrollView {
private LinearLayout container;
private List<Item> items;
public void setItems(List<Item> items) {
this.items = items;
updateView();
}
public CustomLayout(Context context) {
super(context);
init();
}
public CustomLayout(Context context, AttributeSet attrs) {
super(context, attrs);
init();
}
private void init() {
View v = inflate(getContext(), R.layout.custom_layout, this);
container = (LinearLayout) v.findViewById(R.id.container);
updateView();
}
#Override
protected void onSizeChanged(int w, int h, int oldw, int oldh) {
super.onSizeChanged(w, h, oldw, oldh);
updateView();
}
public void updateView() {
if (items == null) {
return;
}
container.removeAllViews();
int width = getWidth();
if (items.size() > 1) {
width = width * 1 / 2;
}
for (final Item item : items) {
View child = LayoutInflator.from(getContext(), R.layout.custom_item, container, false);
child.setLayoutParams(new LinearLayout.LayoutParams(width, LinearLayout.LayoutParams.MATCH_PARENT));
container.addView(child);
}
}
}
I don't know why this way does not always work. Sometimes, the child, container just fails to render for some reason. Seeing the layout hierarchy shows that HorizontalScrollView has no children. What happened to container?
Also, it seems that even when placed in a network call, setItems is called before onSizeChanged, which makes me think that onSizeChanged is the wrong place to do updateView?
Try to place the updateView() method inside onMeasure instead of onSizeChanged, and use getMeasuredWidth() instead of getWidth():
#Override
protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
super.onMeasure(widthMeasureSpec, heightMeasureSpec);
updateView();
}
public void updateView() {
if (items == null) {
return;
}
container.removeAllViews();
int width = getMeasuredWidth();
if (items.size() > 1) {
width = width * 1 / 2;
}
for (final Item item : items) {
View child = LayoutInflator.from(getContext(), R.layout.custom_item, container, false);
child.setLayoutParams(new LinearLayout.LayoutParams(width, LinearLayout.LayoutParams.MATCH_PARENT));
container.addView(child);
}
}
Also, if you call setItems() method from callback of async request, dont forget to run it on UI thread by using runOnUiThread method of Activity.
I am making custom view using custom ViewGroup and custom View in Android.
public class Custom_ViewGroup extends ViewGroup
{
public Custom_ViewGroup(Context context)
{
super(context);
addView(new OwnView(context));
}
public Custom_ViewGroup(Context context,AttributeSet attrs)
{
super(context, attrs);
addView(new OwnView(context));
}
#Override
protected void onLayout(boolean changed, int l, int t, int r, int b)
{
// TODO Auto-generated method stub
}
class OwnView extends View
{
public OwnView(Context context)
{
super(context);
System.out.println("on constructor");
}
#Override
protected void onDraw(Canvas canvas)
{
super.onDraw(canvas);
System.out.println("ondraw child");
}
}
}
onDraw() method of OwnView class is not calling. constructor of OwnView class called. I have used invalidate() method after adding view, but it did not work.
This is your problem
#Override
protected void onLayout(boolean changed, int l, int t, int r, int b)
{
// TODO Auto-generated method stub
}
Your view is never laid-out, so it will not draw. You need to implement the onLayout method properly. Also, if your ViewGroup contains only a single view, consider using FrameLayout instead of ViewGroup.
I'm fairly proficient at creating complex custom layouts based on ViewGroup. The only thing I'm missing is the ability to create my custom LayoutParams. I really need the ability to get the margins and why not create other extra params to pass in to the parent.
How can I go about creating a custom LayoutParam and using it via xml? I tried using a LinearLayout.LayoutParam but it's obviously crashing since the parent is not a LinearLayout. How can I work with LayoutParams on custom layouts?
Update:
As of now I'm sticking with using a FrameLayout and overriding the onMeasure and onLayout functions to do the layout myself. This does provide FrameLayout.LayoutParams. I'm guessing the childs would have to support the custom LayoutParam?
In your custom layout, create a nested class extending ViewGroup.LayoutParams. Then override some methods (all of the required ones are in my example). Here's a stripped-down version of one of my custom layouts:
public class MyLayout extends ViewGroup {
public MyLayout(Context context) {
}
public MyLayout(Context context, AttributeSet attrs) {
}
#Override
protected void onLayout(boolean changed, int l, int t, int r, int b) {
}
#Override
protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
}
#Override
protected boolean checkLayoutParams(ViewGroup.LayoutParams p) {
return p instanceof LayoutParams;
}
#Override
protected LayoutParams generateDefaultLayoutParams() {
return new LayoutParams();
}
#Override
public LayoutParams generateLayoutParams(AttributeSet attrs) {
return new LayoutParams(getContext(), attrs);
}
#Override
protected LayoutParams generateLayoutParams(ViewGroup.LayoutParams p) {
return generateDefaultLayoutParams(); // TODO Change this?
}
public static class LayoutParams extends ViewGroup.LayoutParams {
public LayoutParams() {
}
public LayoutParams(int width, int height) {
}
public LayoutParams(Context context, AttributeSet attrs) {
}
}
}
Further explanation: How to create a FlowLayout (thanks for the link Luksprog!)
I have a frameLayout and inside this layout there are several Views, and I want to margin each view from the top to a specific distance .
I have tried the following code, but it seems that it doesn't work
FrameLayout lytBoard = (FrameLayout) findViewById(R.id.lytBoard);
LinearLayout.LayoutParams params = new LinearLayout.LayoutParams(width, height);
params.setMargins((int)board.cells.get(i).x1, (int)board.cells.get(i).y1, 0, 0);
CellView cv = new CellView(getApplicationContext());
cv.setLayoutParams(params);
lytBoard.addView(cv);
Cell View class:
public class CellView extends View {
public CellView(Context context) {
super(context);
// TODO Auto-generated constructor stub
}
public void onMeasure(int widthMeasureSpec, int heightMeasureSpec){
super.onMeasure(widthMeasureSpec, heightMeasureSpec);
int size = Math.min(getMeasuredWidth(), getMeasuredHeight());
setMeasuredDimension(size, size);
}
}
You are setting to the CellView, subclass of View, a layoutParams of the type LinearLayout.LayoutParams. In the definition of the method in View, the method setLayoutParams receives params of the type ViewGroup.LayoutParams and ViewGroup does not contains a margin property, so this may be the cause of the problem.
You can try to subclass LinearLayout instead of View.
I want to use getWidth()/getHeight() to get width/height of my XML-Layout.
I read I have to do it in the method onSizeChanged() otherwise I will get 0
( Android: Get the screen resolution / pixels as integer values ).
But I want to do it in a class already extending Activity.
So I think it's not possible let the same class extending View.
public class MyClass extends Activity {
public void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.main);
ViewGroup xml_layout = (ViewGroup) findViewById(R.id.layout_id);
TextView tv = new TextView(this);
tv = (TextView) findViewById(R.id.text_view);
int layout_height = xml_layout.getHeight();
int layout_width = xml_layout.getWidth();
}
protected void onSizeChanged(int w, int h, int oldw, int oldh) {
//Error, because I need to use extends View for class, but I can't do it because I also need extends Activity to use onCreate
}
}
If I use MyClass extends Activity I can use onCreate but not onSizeChanged.
If I use MyClass extends View I can use onSizeChangedbut not onCreate.
How can I solve the problem?
You dont have to create a customView to get its height and width. You can add an OnLayoutChangedListener (description here) to the view whose width/height you want, and then essentially get the values in the onLayoutChanged method, like so
View myView = findViewById(R.id.my_view);
myView.addOnLayoutChangeListener(new OnLayoutChangeListener() {
#Override
public void onLayoutChange(View v, int left, int top, int right, int bottom, int oldLeft, int oldTop, int oldRight,
int oldBottom) {
// its possible that the layout is not complete in which case
// we will get all zero values for the positions, so ignore the event
if (left == 0 && top == 0 && right == 0 && bottom == 0) {
return;
}
// Do what you need to do with the height/width since they are now set
}
});
The reason for this is because views are drawn only after the layout is complete. The system then walks down the view heirarchy tree to measure the width/height of each view before drawing them.
What I would recommend doing is extending ListView in your own custom class. Define a class called MyListView, or something similar, and make sure you define all three constructors. Then, override the onSizeChanged method to call something externally - much like an OnClickListener or OnTouchListener. You could define a method in MyListView to accept a listener, instantiate a listener in your activity, and then when onSizeChanged is called, pass it on through to the listener. This is really hard to explain in English. Here is some sample code:
Custom ListView:
public class MyListView extends ListView
{
public MyListView(Context context)
{
super(context);
}
public MyListView(Context context, AttributeSet attrs)
{
super(context, attrs);
}
public MyListView(Context context, AttributeSet attrs, int defStyle)
{
super(context, attrs, defStyle);
}
public void SetOnResizeListener(MyOnResizeListener orlExt)
{
orl = orlExt;
}
#Override
protected void onSizeChanged(int xNew, int yNew, int xOld, int yOld)
{
super.onSizeChanged(xNew, yNew, xOld, yOld);
if(orl != null)
{
orl.OnResize(this.getId(), xNew, yNew, xOld, yOld);
}
}
MyOnResizeListener orl = null;
}
Custom listener:
public class MyOnResizeListener
{
public MyOnResizeListener(){}
public void OnResize(int id, int xNew, int yNew, int xOld, int yOld){}
}
You instantiate the listener like:
Class MyActivity extends Activity
{
/***Stuff***/
MyOnResizeListener orlResized = new MyOnResizeListener()
{
#Override
public void OnResize(int id, int xNew, int yNew, int xOld, int yOld)
{
/***Handle resize event****/
}
};
}
And don't forget to pass your listener to your custom view:
/***Probably in your activity's onCreate***/
((MyListView)findViewById(R.id.MyList)).SetOnResizeListener(orlResized);
Finally, you can add your custom list to an XML layout by doing something like:
<com.myapplication.whatever.MyListView>
<!-- Attributes -->
<com.myapplication.whatever.MyListView/>
I had a similar problem (ie. calculating View sizes in an Activity after it was finished drawing).
I overrode the Activity method : public void onWindowFocusChanged(boolean hasFocus)
and it worked fine.
Just add a method to your custom view that you call when onSizeChanged takes place in the Activity. Pass the new values to the view as parameters of the called method. Then execute whatever operations need to take place when your custom view changes size.
i think i know what you want to know........... i just wrote a new blog post on that right now...........
how to get width and height dimensions of a customView (extends View) in Android http://syedrakibalhasan.blogspot.com/2011/02/how-to-get-width-and-height-dimensions.html