add view to customview and resize that - android

i have a customview that extend from FrameLayout:
public class FanView extends FrameLayout {
private static final String TAG = FanView.class.getSimpleName();
private List<FanItem> fanItems = new ArrayList<>();
private float openRatio = 0f;
public FanView(Context context, AttributeSet attrs, int defStyleAttr) {
super(context, attrs, defStyleAttr);
init();
}
private void init() {
setStaticTransformationsEnabled(true);
}
#Override
protected void onLayout(boolean changed, int left, int top, int right, int bottom) {
Log.d(TAG, "onLayout(" + changed + ", " + left + ", " + top + ", " + right + ", " + bottom + ")");
super.onLayout(changed, left, top, right, bottom);
Log.d(TAG, "End onLayout");
}
#Override
protected boolean getChildStaticTransformation(View child, Transformation t) {
final float index = getChildCount() - indexOfChild(child) - 1;
final float height = child.getHeight();
Matrix matrix = t.getMatrix();
matrix.setTranslate((float) (-index * 1.1 * (height/2) * openRatio), (float) (index * height * openRatio * 1.2));
return true;
}
public void setOpenRatio(float r) {
this.openRatio = r;
final int childCount = getChildCount();
for (int i = 0; i < childCount; i++) {
View view = getChildAt(i);
view.invalidate();
}
invalidate();
}
public void setFanItems(List<FanItem> fanItems) {
this.fanItems = fanItems;
removeAllViewsInLayout();
for (int i = 0; i < fanItems.size(); i++) {
int fanItemIndex = fanItems.size() - i - 1;
FanItem fanItem = fanItems.get(fanItemIndex);
View fanView = inflate(getContext(),
fanItemIndex == 0 ? R.layout.fan_item_header : R.layout.fan_item, null);
TextView textView = (TextView) fanView.findViewById(R.id.fan_view_item_title);
textView.setText(fanItem.getTitle());
addViewInLayout(fanView, i,
new LayoutParams(
ViewGroup.LayoutParams.WRAP_CONTENT,
ViewGroup.LayoutParams.WRAP_CONTENT));
}
invalidate();
}
}
in xml layout i use this custom view with width and height wrap_content.
when i add view to that custom view not show this views,
because custom layout not change it's size.
how to say to custom view that change it's size

Your current code probably needs another constructor
public FanView(Context context, AttributeSet attrs) {
super(context, attrs);
init();
}

Please provide valid size to inflate the items at first. Give at least one element to acquire size after layout inflation. You can change the size of items using layout params.
<?xml version="1.0" encoding="utf-8"?>
<FrameLayout
xmlns:android="schemas.android.com/apk/res/android"
android:background="#drawable/fan_item_background"
android:layout_height="wrap_content"
android:layout_width="wrap_content"
android:textDirection="rtl">
<TextView
android:id="#+id/fan_view_item_title"
android:layout_height="200dp"
android:layout_width="200dp"
android:text="ersfsdf"
android:textAlignment="textStart"
android:textColor="#ffffff"/>
</FrameLayout>
To change the size dynamically try something like this .
// Gets linearlayout
FrameLayout layout = findViewById(R.id.framelaut);// or any layout
// Gets the layout params that will allow you to resize the layout
LayoutParams params = layout.getLayoutParams();
// Changes the height and width to the specified *pixels*
params.height = 100;
params.width = 100;
layout.setLayoutParams(params);

Related

Auto Sizing Two Text Views to display as much content as possible

There are two text views inside a linear layout with horizontal orientation.
LeftText | RightText
Objective is to make best use of the screen space and display both the text values as much as possible. If the text exceeds the textview size, they should be truncated with ellipsis. If one of them is short, the other one should expand to the remaining size.
For e.g.
Input - Short | Short
Expected Output Short | Short
Input - Short | LongLongLongLong
Expected Output Short | Long...
Input - LongLongLongLong | Short
Expected Output LongLong...|Short
Input - LongLongLongLong | LongLongLongLong
Expected Output LongLong...|LongLong...
I have tried to create a custom view AutoSizingTextLayout (extends LinearLayout) which consists of two custom Text Views with initial equal layout weight = 1. I have added a onDrawListener for the second Text View (one on the right). As soon as the second text view is done drawing, I adjust the weights depending on which text was truncated.
This logic works but not reliably. On some runs, it runs perfectly but most times, the first text view takes the entire screen.
I am pasting relevant part of my code below. Is there something that needs to be handled? Is there a more elegant solution?
PS: This custom view is being used inside a list recycler view.
public class AutoSizingTextLayout extends LinearLayout implements
AutoSizingTextView.AutoSizingTextViewDrawListener {
#BindView(R.id.start_text_view)
AutoSizingTextView mStartTextView;
#BindView(R.id.end_text_view)
AutoSizingTextView mEndTextView;
private volatile boolean adjusted = false;
//Constructors
#Override
public void onDraw() {
if (!adjusted) {
adjustWeights();
}
}
public void setStartViewText(String startText) {
mStartTextView.setText(startText);
}
public void setEndViewText(String endText) {
adjusted = false;
mEndTextView.setText(endText);
}
private void initView(AttributeSet attrs) {
LayoutInflater inflater = LayoutInflater.from(getContext());
inflater.inflate(R.layout.auto_sizing_text_layout, this);
ButterKnife.bind(this);
mEndTextView.setDrawListener(this);
}
private void adjustWeights() {
boolean startTextHasEllipses = hasEllipses(mStartTextView);
boolean endTextHasEllipses = hasEllipses(mEndTextView);
ArrayList<AutoSizingTextView> viewsToUpdate = new ArrayList<>();
if (startTextHasEllipses && endTextHasEllipses) {
//Both are truncated. Do nothing.
} else if (startTextHasEllipses) {
// Starting text is truncated. End text is not truncated. Give more weight to starting text.
viewsToUpdate.add(mEndTextView);
} else if (endTextHasEllipses) {
// Starting text is not truncated. End text is truncated. Give more weight to ending text.
viewsToUpdate.add(mStartTextView);
} else {
// Both are not truncated. Set it to wrap content.
viewsToUpdate.add(mStartTextView);
viewsToUpdate.add(mEndTextView);
}
updateView(viewsToUpdate);
}
private void updateView(List<AutoSizingTextView> viewsToUpdate) {
for (AutoSizingTextView viewToUpdate : viewsToUpdate) {
LinearLayout.LayoutParams params = (LinearLayout.LayoutParams)
viewToUpdate.getLayoutParams();
params.width = LinearLayout.LayoutParams.WRAP_CONTENT;
params.weight = 0.0F;
adjusted = true;
viewToUpdate.setLayoutParams(params);
}
this.requestLayout();
}
private static boolean hasEllipses(TextView textView) {
Layout layout = textView.getLayout();
if (layout != null) {
int lines = layout.getLineCount();
if (lines > 0) {
if (layout.getEllipsisCount(lines - 1) > 0) {
return true;
}
}
}
return false;
}
}
//Custom Text View
class AutoSizingTextView extends
android.support.v7.widget.AppCompatTextView {
private AutoSizingTextViewDrawListener mDrawListener;
//Constructors
public void setDrawListener(AutoSizingTextViewDrawListener drawListener) {
mDrawListener = drawListener;
}
public interface AutoSizingTextViewDrawListener {
void onDraw();
}
#Override
protected void onDraw(Canvas canvas) {
super.onDraw(canvas);
if (mDrawListener != null) {
mDrawListener.onDraw();
}
}
}
//auto_sizing_text_layout.xml
<LinearLayout
android:layout_width="wrap_content"
android:layout_height="wrap_content"
xmlns:android="http://schemas.android.com/apk/res/android">
<AutoSizingTextView
android:id="#+id/start_text_view"
android:layout_width="0dp"
android:layout_height="wrap_content"
android:layout_weight="1"
android:maxLines="1"
android:ellipsize="end"/>
<AutoSizingTextView
android:id="#+id/end_text_view"
android:layout_width="0dp"
android:layout_height="wrap_content"
android:layout_weight="1"
android:maxLines="1"
android:ellipsize="end"/>
I suppose following is the expected result.
I wrote a simple ViewGroup for this. Of course, it is not perfect. It just demonstrates that it is achievable. Modify this code accordingly. I have overridden the onLayout() method of the ViewGroup where I'm computing the required size of the subviews and laying out them accordingly. This WeirdView contains three TextViews for (left text, a separator, and a right text) respectively.
package com.example.abhinav.textviewexample;
import android.content.Context;
import android.support.annotation.Nullable;
import android.util.AttributeSet;
import android.view.ViewGroup;
import android.widget.LinearLayout;
import android.widget.TextView;
/**
* Created by abhinav on 16/04/18.
*/
public class WeirdView extends ViewGroup {
private TextView mLeftTV;
private TextView mSeparatorTV;
private TextView mRightTV;
public WeirdView(Context context) {
this(context, null, 0);
}
public WeirdView(Context context, #Nullable AttributeSet attrs) {
this(context, attrs, 0);
}
public WeirdView(Context context, #Nullable AttributeSet attrs, int defStyleAttr) {
super(context, attrs, defStyleAttr);
init(context);
}
private void init(Context context) {
mLeftTV = new TextView(context);
mSeparatorTV = new TextView(context);
mRightTV = new TextView(context);
mLeftTV.setLayoutParams(new LinearLayout.LayoutParams(ViewGroup.LayoutParams.WRAP_CONTENT, ViewGroup.LayoutParams.WRAP_CONTENT));
mSeparatorTV.setLayoutParams(new LinearLayout.LayoutParams(ViewGroup.LayoutParams.WRAP_CONTENT, ViewGroup.LayoutParams.WRAP_CONTENT));
mRightTV.setLayoutParams(new LinearLayout.LayoutParams(ViewGroup.LayoutParams.WRAP_CONTENT, ViewGroup.LayoutParams.WRAP_CONTENT));
addView(mLeftTV);
addView(mSeparatorTV);
addView(mRightTV);
}
public TextView getLeftTextView() {
return mLeftTV;
}
public TextView getSeparatorTextView() {
return mSeparatorTV;
}
public TextView getRightTextView() {
return mRightTV;
}
#Override
protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
super.onMeasure(widthMeasureSpec, heightMeasureSpec);
}
#Override
protected void onLayout(boolean changed, int l, int t, int r, int b) {
// super.onLayout(changed, l, t, r, b);
final int childLeft = this.getPaddingLeft();
final int childTop = this.getPaddingTop();
final int childRight = this.getMeasuredWidth() - this.getPaddingRight();
final int childBottom = this.getMeasuredHeight() - this.getPaddingBottom();
final int availableWidth = childRight - childLeft;
final int availableHeight = childBottom - childTop;
mLeftTV.measure(MeasureSpec.makeMeasureSpec(availableWidth, MeasureSpec.AT_MOST), MeasureSpec.makeMeasureSpec(availableHeight, MeasureSpec.AT_MOST));
mSeparatorTV.measure(MeasureSpec.makeMeasureSpec(availableWidth, MeasureSpec.AT_MOST), MeasureSpec.makeMeasureSpec(availableHeight, MeasureSpec.AT_MOST));
mRightTV.measure(MeasureSpec.makeMeasureSpec(availableWidth, MeasureSpec.AT_MOST), MeasureSpec.makeMeasureSpec(availableHeight, MeasureSpec.AT_MOST));
// case: |blabla > blablabla |
// case: |blablablablablabla > bla |
// case: |bla > blablablablablablab..|
if ((mLeftTV.getMeasuredWidth() + mSeparatorTV.getMeasuredWidth() + mRightTV.getMeasuredWidth() <= availableWidth)
|| (mLeftTV.getMeasuredWidth() <= availableWidth / 2)) {
int left = childLeft;
mLeftTV.layout(left, childTop, left + mLeftTV.getMeasuredWidth(), childBottom);
left += mLeftTV.getMeasuredWidth();
mSeparatorTV.layout(left, childTop, left + mSeparatorTV.getMeasuredWidth(), childBottom);
left += mSeparatorTV.getMeasuredWidth();
mRightTV.layout(left, childTop, childRight, childBottom);
mRightTV.setWidth(childRight - left);
}
// case: |blablablablablabla.. > blab|
else if (mRightTV.getMeasuredWidth() <= availableWidth / 2) {
int left = childLeft;
mLeftTV.layout(left, childTop, childRight - mSeparatorTV.getMeasuredWidth() - mRightTV.getMeasuredWidth(), childBottom);
mLeftTV.setWidth(childRight - mSeparatorTV.getMeasuredWidth() - mRightTV.getMeasuredWidth() - left);
left += mLeftTV.getWidth();
mSeparatorTV.layout(left, childTop, mSeparatorTV.getMeasuredWidth(), childBottom);
left += mSeparatorTV.getMeasuredWidth();
mRightTV.layout(left, childTop, mRightTV.getMeasuredWidth(), childBottom);
}
// case: |blablabla.. > blablablabl..|
else {
int left = childLeft;
mLeftTV.layout(left, childTop, left + availableWidth / 2 - mSeparatorTV.getMeasuredWidth() / 2, childBottom);
mLeftTV.setWidth(availableWidth / 2 - mSeparatorTV.getMeasuredWidth() / 2);
left += (availableWidth / 2 + mSeparatorTV.getMeasuredWidth() / 2);
mSeparatorTV.layout(left, childTop, left + mSeparatorTV.getMeasuredWidth(), childBottom);
left += mSeparatorTV.getMeasuredWidth();
mRightTV.layout(left, childTop, childRight, childBottom);
mRightTV.setWidth(childRight - left);
}
}
}

Compound child view not shown inside custom ViewGroup

I'm coding a ring menu that is going to put together several different types of view, which will have all the same size. The thing is that it works perfectly with native views, like ImageView, but when I try to put a custom labeled image view, it simply doesn't appear int the custom ViewGroup. It's also worth mentioning that when this view is declared in the XML file, outside de custom ViewGroup it is shown just fine, but as soon as I put it inside the ViewGroup, or declare it programatically, it vanishes. My guess is that I'm doing something weong inside the onLayout method, but I can't put my finger on it, since all coordinates and view sizes are correct according to the Log.
The XML file for the CompoundView:
<?xml version="1.0" encoding="utf-8"?>
<RelativeLayout xmlns:android="http://schemas.android.com/apk/res/android"
android:orientation="vertical" android:layout_width="match_parent"
android:layout_height="match_parent"
android:clickable="true"
android:focusable="true">
<ImageView
android:id="#+id/header"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_alignParentLeft="true"
android:layout_centerVertical="true"
android:src="#drawable/ropeiconselector"/>
<TextView
android:id="#+id/label"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="WWWWWWWWWWWW"
android:background="#color/button"
android:layout_marginLeft="-10dp"
android:layout_centerVertical="true"
android:layout_toRightOf="#id/header"
android:padding="8dp"
/>
The code for the CompoundView (I omitted some unimportant methods)
public class CircularLabeledImageView extends RelativeLayout implements View.OnClickListener {
ImageView headerView;
TextView labelView;
boolean isOpen = false;
String[] itemDesc;
int position;
int size;
int maxLabelWidth = 100;
int minLabelWidth = 20;
final Handler timeHandler = new Handler();
Runnable toggleTimer;
public CircularLabeledImageView(Context context) {
super(context);
//EDIT Methhod called
initView(context);
}
public CircularLabeledImageView(Context context, AttributeSet attrs) {
super(context, attrs);
initView(context);
}
public CircularLabeledImageView(Context context, AttributeSet attrs, int defStyleAttr) {
super(context, attrs, defStyleAttr);
initView(context);
}
private void initView(Context context){
LayoutInflater inflater = (LayoutInflater)context.getSystemService(Context.LAYOUT_INFLATER_SERVICE);
inflater.inflate(R.layout.expandabletextimageview, this);
headerView = (ImageView) this.findViewById(R.id.header);
labelView = (TextView) this.findViewById(R.id.label);
labelView.setBackgroundResource(R.color.backgroundMenuContents);
headerView.setOnClickListener(this);
itemDesc = new String[]{"Item A","Item B", "Item C","Quantos itens"};
size = itemDesc.length;
toggleTimer = new Runnable() {
#Override
public void run() {
toggle();
}
};
this.setOnFocusChangeListener(new OnFocusChangeListener() {
#Override
public void onFocusChange(View v, boolean hasFocus) {
if (isOpen) {
toggle();
}
}
});
}
}
The code for the custom ViewGroup
public class RingListMenu extends ViewGroup implements View.OnClickListener {
boolean isOpen = true;
int headerSize= 90;
int childSize= 80;
int radiusInit = 150;
int childInitSize = 80;
int radius = 150;
int padding;
DPoint center = new DPoint();
float startingAngle= 2;
DPoint click0;
DPoint clickIni;
DPoint clickFinal;
final static float SENSIBILITY = 10f;
final static float FRICTION = 0.00001f;
final static float MAXVELOCITY = 0.06f;
final static long TOGGLE_DURATION = 300;
private VelocityTracker vTracker = null;
boolean isScrolling;
ImageView circleView;
OnItemClickListener listener = null;
public RingListMenu(Context context, AttributeSet attrs, int defStyleAttr, int defStyleRes) {
super(context, attrs, defStyleAttr, defStyleRes);
if (android.os.Build.VERSION.SDK_INT >= 11)
{
setLayerType(View.LAYER_TYPE_SOFTWARE, null);
}
TypedArray at = context.obtainStyledAttributes(attrs,R.styleable.RingListMenu);
childInitSize = childSize = at.getDimensionPixelSize(R.styleable.RingListMenu_childSize, 0);
radiusInit = radius = at.getDimensionPixelSize(R.styleable.RingListMenu_circleRadius, 0);
headerSize = at.getDimensionPixelSize(R.styleable.RingListMenu_headerSize, 0);
padding = at.getDimensionPixelSize(R.styleable.RingListMenu_padding, 0);
}
public RingListMenu(Context context, AttributeSet attrs, int defStyleAttr) {
super(context, attrs, defStyleAttr);
if (android.os.Build.VERSION.SDK_INT >= 11)
{
setLayerType(View.LAYER_TYPE_SOFTWARE, null);
}
TypedArray at = context.obtainStyledAttributes(attrs,R.styleable.RingListMenu);
childInitSize = childSize = at.getDimensionPixelSize(R.styleable.RingListMenu_childSize, 0);
radiusInit = radius = at.getDimensionPixelSize(R.styleable.RingListMenu_circleRadius, 0);
headerSize = at.getDimensionPixelSize(R.styleable.RingListMenu_headerSize, 0);
padding = at.getDimensionPixelSize(R.styleable.RingListMenu_padding, 0);
}
public RingListMenu(Context context, AttributeSet attrs) {
super(context, attrs);
if (android.os.Build.VERSION.SDK_INT >= 11)
{
setLayerType(View.LAYER_TYPE_SOFTWARE, null);
}
TypedArray at = context.obtainStyledAttributes(attrs, R.styleable.RingListMenu);
childInitSize = childSize = at.getDimensionPixelSize(R.styleable.RingListMenu_childSize, 0);
radiusInit = radius = at.getDimensionPixelSize(R.styleable.RingListMenu_circleRadius, 0);
headerSize = at.getDimensionPixelSize(R.styleable.RingListMenu_headerSize, 0);
padding = at.getDimensionPixelSize(R.styleable.RingListMenu_padding, 0);
}
#Override
protected void onLayout(boolean changed, int l, int t, int r, int b) {
Log.d("RingList", "Calling Ring list onLayout" + childSize + " " + radius + " " + headerSize);
float angle = (float) (2*Math.PI)/(getChildCount()-1);
center.x = padding+headerSize/2;
center.y = padding+headerSize/2;
float childX;
float childY;
//radius = (float) (getChildCount()*(minSpacing+2*childSize)/(2*Math.PI));
for (int i = 1; i < getChildCount(); i++) {
View child = getChildAt(i);
childX = (float) (center.x + radius * Math.cos(startingAngle + i * angle));
childY = (float) (center.y + radius * Math.sin(startingAngle + i * angle));
child.layout((int) (childX - childSize / 2), (int) (childY - childSize / 2),
(int) (childX + childSize / 2), (int) (childY + childSize / 2));
}
View header = getChildAt(0);
header.setX(padding);
header.setY(padding);
header.layout(padding, padding, padding + headerSize, padding + headerSize);
}
#Override
public void addView(View child) {
child.setTag(getChildCount());
super.addView(child);
child.setOnClickListener(this);
}
}
And finally, the declaration part:
RingListMenu ring = (RingListMenu) findViewById(R.id.ring);
CircularLabeledImageView ViewA = new CircularLabeledImageView(this);
ring.addView(ViewA);

Is it possible to implement a FlowLayout with RelativeLayout properties?

I would like to create a custom RelativeLayout that has two views in one row: one on the left side of the screen (android:layout_alignParentStart="true") and one on the right (android:layout_alignParentEnd="true"). The view on the right will grow toward the left view until it takes up all the space between the two views. Then it will move to a new line under the view on the left.
I have implemented a slightly modified version of Romain Guy's FlowLayout that extends RelativeLayout. However, this class seems to ignore the RelativeLayout's align properties and just sticks the views right next to each other. Is there a way to implement a such a layout that will anchor the views to the left and right?
FlowLayout class:
public class FlowLayout extends RelativeLayout {
private int mHorizontalSpacing;
private int mVerticalSpacing;
public FlowLayout(Context context) {
super(context);
}
public FlowLayout(Context context, AttributeSet attrs) {
super(context, attrs);
TypedArray attributes = context.obtainStyledAttributes(attrs, R.styleable.FlowLayout);
mHorizontalSpacing = attributes.getDimensionPixelSize(R.styleable
.FlowLayout_horizontalSpacing, 0);
mVerticalSpacing = attributes.getDimensionPixelSize(R.styleable
.FlowLayout_verticalSpacing, 0);
attributes.recycle();
}
#Override
protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
int widthSize = MeasureSpec.getSize(widthMeasureSpec);
int width = 0;
int height = getPaddingTop();
int currentWidth = getPaddingStart();
int currentHeight = 0;
final int count = getChildCount();
for (int i = 0; i < count; i++) {
View child = getChildAt(i);
LayoutParams lp = (LayoutParams) child.getLayoutParams();
measureChild(child, widthMeasureSpec, heightMeasureSpec);
if (currentWidth + child.getMeasuredWidth() > widthSize) {
height += currentHeight + mVerticalSpacing;
currentHeight = 0;
width = Math.max(width, currentWidth);
currentWidth = getPaddingEnd();
}
int spacing = mHorizontalSpacing;
if (lp.spacing > -1) {
spacing = lp.spacing;
}
lp.x = currentWidth + spacing;
lp.y = currentHeight;
currentWidth += child.getMeasuredWidth();
currentHeight = Math.max(currentHeight, child.getMeasuredHeight());
}
width += getPaddingEnd();
height += getPaddingBottom();
setMeasuredDimension(resolveSize(width, widthMeasureSpec), resolveSize(height,
heightMeasureSpec));
}
#Override
protected void onLayout(boolean changed, int l, int t, int r, int b) {
final int count = getChildCount();
for (int i = 0; i < count; i++) {
View child = getChildAt(i);
LayoutParams lp = (LayoutParams) child.getLayoutParams();
child.layout(lp.x, lp.y, lp.x + child.getMeasuredWidth(), lp.y + child
.getMeasuredHeight());
}
}
#Override
protected boolean checkLayoutParams(ViewGroup.LayoutParams p) {
return p instanceof LayoutParams;
}
#Override
protected ViewGroup.LayoutParams generateDefaultLayoutParams() {
return new LayoutParams(RelativeLayout.LayoutParams.WRAP_CONTENT, RelativeLayout
.LayoutParams.WRAP_CONTENT);
}
#Override
protected ViewGroup.LayoutParams generateLayoutParams(ViewGroup.LayoutParams p) {
return new LayoutParams(p.width, p.height);
}
#Override
public RelativeLayout.LayoutParams generateLayoutParams(AttributeSet attrs) {
return new LayoutParams(getContext(), attrs);
}
public static class LayoutParams extends RelativeLayout.LayoutParams {
public int spacing;
public int x;
public int y;
public LayoutParams(Context context, AttributeSet attrs) {
super(context, attrs);
TypedArray attributes = context.obtainStyledAttributes(attrs, R.styleable
.FlowLayout_LayoutParams);
spacing = attributes.getDimensionPixelSize(R.styleable
.FlowLayout_LayoutParams_layoutSpacing, -1);
attributes.recycle();
}
public LayoutParams(int width, int height) {
super(width, height);
}
}
}
It turns out that rather than calculating the right view's new position yourself, you can change its LayoutParams and have the OS handle positioning for you. I created a custom layout that extends RelativeLayout and overrides the onMeasure() method. This will adjust the LayoutParams accordingly.
More specifically:
Call the super method then find the widths of the two views and their parent in onMeasure(). Use these to figure out if the right view will overlap the left view. If so, change the right view's layout_alignParentEnd="true" property to be layout_alignParentStart="true" and give it the layout_below="#id/left_view" property. Do the opposite when there will be no overlap. Call the super method again to have the OS remeasure the views for you.
The layout class:
public class WrappingLayout extends RelativeLayout {
private TextView leftView;
private EditText rightView;
//Use this to prevent unnecessarily adjusting the LayoutParams
//when the right view is already in the correct position
private boolean isMultiline = false;
public WrappingLayout(Context context) {
super(context);
LayoutInflater inflater = LayoutInflater.from(context);
inflater.inflate(R.layout.wrapping_layout, this);
leftView = (TextView) findViewById(R.id.left_view);
rightView = (EditText) findViewById(R.id.right_view);
}
public WrappingLayout(Context context, AttributeSet attrs) {
super(context, attrs);
LayoutInflater inflater = LayoutInflater.from(context);
inflater.inflate(R.layout.wrapping_layout, this);
leftView = (TextView) findViewById(R.id.left_view);
rightView = (EditText) findViewById(R.id.right_view);
}
#Override
protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
//Call first to make sure the views' initial widths have been set
super.onMeasure(widthMeasureSpec, heightMeasureSpec);
int screenWidth = getMeasuredWidth();
int leftViewWidth = getPaddingStart() + leftView.getMeasuredWidth() + leftView.getPaddingEnd();
int rightViewWidth = getPaddingEnd() + rightView.getMeasuredWidth() + rightView.getPaddingStart();
LayoutParams rightViewParams = (LayoutParams) rightView.getLayoutParams();
if (!isMultiline && rightViewWidth + leftViewWidth > screenWidth) {
isMultiline = true;
rightViewParams.addRule(BELOW, R.id.left_view);
rightViewParams.removeRule(ALIGN_PARENT_END);
rightViewParams.addRule(ALIGN_PARENT_START);
//Call again here to adjust dimensions for new params
super.onMeasure(widthMeasureSpec, heightMeasureSpec);
} else if (isMultiline && rightViewWidth + leftViewWidth < screenWidth) {
isMultiline = false;
rightViewParams.removeRule(BELOW);
rightViewParams.addRule(ALIGN_PARENT_END);
rightViewParams.removeRule(ALIGN_PARENT_START);
//Call again here to adjust dimensions for new params
super.onMeasure(widthMeasureSpec, heightMeasureSpec);
}
}
}
The layout XML:
<?xml version="1.0" encoding="utf-8"?>
<merge
xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:app="http://schemas.android.com/apk/res-auto"
android:layout_width="match_parent"
android:layout_height="match_parent">
<TextView
android:id="#id/left_view"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_alignParentStart="true"
android:layout_centerVertical="true"/>
<EditText
android:id="#id/right_view"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_alignParentEnd="true"
android:gravity="center"
android:text="#string/hello"/>
</merge>

Android: How can I add childs to my custom ViewGroup with a CursorAdapter?

Please help! I tried everything! :(
I've got a schedule Class, which is simply a custom ViewGroup (with custom onMeasure() and onLayout()), which enables me to place childs(=events) with LayoutParams for column/row start and column/row end. The number of childs and their LayoutParams depend on database entries.
Now I'm trying to add childs (events) from my database. I'd have to use a Cursor Adapter, so my schedule Class has to extend ListView, right? I tried that but the newView() method of the adapter is never called. Why not??
My custom ListView doesn't ask the adapter for childs, no childs are added. I also can't add the childs by hand calling schedule.addView() if I extend from AdapterView.
I'd be really (really) happy if someone could help!
Regards,
cody
This is my custom ViewGroup:
public class Schedule extends ViewGroup {
private int columns;
private int rows;
private float preferredCellWidth;
private float preferredCellHeight;
private String[] rowTimes;
private Paint paint;
public Schedule(Context context, int columns, int rows, float preferredCellWidth, float preferredCellHeight, String[] rowTimes) {
super(context);
this.columns = columns;
this.rows = rows;
this.preferredCellWidth = preferredCellWidth;
this.preferredCellHeight = preferredCellHeight;
this.rowTimes = rowTimes;
init(context);
}
private void init(Context context) {
Log.i("Schedule", "initSchedule...");
setPaint();
setWillNotDraw(false);
}
private void setPaint() {
paint = new Paint();
paint.setTextSize(preferredCellHeight*2/3);
paint.setStyle(Paint.Style.STROKE);
paint.setColor(getResources().getColor(R.color.white));
}
public Schedule(Context context, AttributeSet attrs) {
super(context, attrs);
readAttr(context, attrs);
init(context);
}
public Schedule(Context context, AttributeSet attrs, int defStyle) {
super(context, attrs, defStyle);
readAttr(context, attrs);
init(context);
}
private void readAttr(Context c, AttributeSet attrs) {
android.content.res.TypedArray a = c.obtainStyledAttributes(attrs, R.styleable.ScheduleLayout);
this.columns = a.getInt(R.styleable.ScheduleLayout_columns, 1);
this.rows = a.getInt(R.styleable.ScheduleLayout_rows, 1);
this.preferredCellWidth = a.getDimension(R.styleable.ScheduleLayout_preferredCellWidth, 1);
this.preferredCellHeight = a.getDimension(R.styleable.ScheduleLayout_preferredCellHeight, 1);
a.recycle();
}
#Override
protected void onDraw(Canvas canvas) {
//Log.i(this.toString(),"onDraw ..."+" this.getLeft()="+this.getLeft()+", this.getWidth()="+this.getWidth());
super.onDraw(canvas);
for (int i = 0; i < rows; i++) {
int line = (int) Math.round(this.getTop()+ (i+1) * preferredCellHeight);
canvas.drawText(this.rowtimes[i], this.getLeft()+5, line-3, paint);
canvas.drawLine(this.getLeft(), line, this.getWidth(), line, paint);
}
}
#Override
protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
Log.i("Schedule", "onMeasure...");
float width = (MeasureSpec.getSize(widthMeasureSpec) - getPaddingLeft() - getPaddingRight()) / columns;
float height = (MeasureSpec.getSize(heightMeasureSpec) - getPaddingTop() - getPaddingBottom()) / rows;
float cellWidth = preferredCellWidth;
float cellHeight = preferredCellHeight;
if (MeasureSpec.getMode(widthMeasureSpec) == MeasureSpec.EXACTLY) {
cellWidth = width;
} else if (MeasureSpec.getMode(widthMeasureSpec) == MeasureSpec.AT_MOST) {
cellWidth = Math.min(preferredCellWidth, width);
}
if (MeasureSpec.getMode(heightMeasureSpec) == MeasureSpec.EXACTLY) {
cellHeight = height;
} else if (MeasureSpec.getMode(heightMeasureSpec) == MeasureSpec.AT_MOST) {
cellHeight = Math.min(preferredCellHeight, height);
}
for (int i = 0; i < getChildCount(); i++) {
View child = getChildAt(i);
if (child.getVisibility() != GONE) {
LayoutParams lp = (LayoutParams) child.getLayoutParams();
int cwidth = (int) Math.round(cellWidth * lp.getWidth());
int cheight = (int) Math.round(cellHeight * lp.getHeight());
child.measure(
MeasureSpec.makeMeasureSpec(cwidth, MeasureSpec.EXACTLY),
MeasureSpec.makeMeasureSpec(cheight, MeasureSpec.EXACTLY)
);
}
}
setMeasuredDimension(
(int) Math.round(cellWidth * columns + getPaddingLeft() + getPaddingRight()),
(int) Math.round(cellHeight * rows + getPaddingTop() + getPaddingBottom())
);
}
#Override
protected void onLayout(boolean changed, int l, int t, int r, int b) {
if (!changed)
return;
int cellWidth = ((r-l) - getPaddingLeft() - getPaddingRight()) / columns;
int cellHeight = ((b-t) - getPaddingTop() - getPaddingBottom()) / rows;
for (int i = 0; i < getChildCount(); i++) {
View child = getChildAt(i);
if (child.getVisibility() != GONE) {
LayoutParams lp = (LayoutParams) child.getLayoutParams();
int cl = (int) Math.round(getPaddingLeft() + lp.columnStart * cellWidth);
int cr = (int) Math.round(getPaddingLeft() + lp.columnEnd * cellWidth);
int ct = (int) Math.round(getPaddingTop() + lp.rowStart * cellHeight);
int cb = (int) Math.round(getPaddingTop() + lp.rowEnd * cellHeight);
child.layout(cl, ct, cr, cb);
}
}
}
protected boolean checkLayoutParams(android.view.ViewGroup.LayoutParams p) {
Log.i("Schedule", "checkLayoutParams...");
if (p instanceof LayoutParams) {
LayoutParams lp = (LayoutParams) p;
if (lp.columnEnd > columns || lp.columnStart < 0)
return false;
if (lp.rowEnd > rows || lp.rowStart < 0)
return false;
return lp.columnEnd > lp.columnStart && lp.rowEnd > lp.rowStart;
} else
return false;
}
public android.widget.AbsListView.LayoutParams generateLayoutParams(AttributeSet attrs) {
return new android.widget.AbsListView.LayoutParams(getContext(), attrs);
}
public static class LayoutParams extends android.view.ViewGroup.LayoutParams {
public int columnStart;
public int columnEnd;
public int rowStart;
public int rowEnd;
public LayoutParams(int columnStart, int rowStart, int columnEnd, int rowEnd) {
super(WRAP_CONTENT, WRAP_CONTENT);
this.columnStart = columnStart;
this.columnEnd = columnEnd;
this.rowStart = rowStart;
this.rowEnd = rowEnd;
}
public LayoutParams(Context c, AttributeSet attrs) {
super(WRAP_CONTENT, WRAP_CONTENT);
android.content.res.TypedArray a = c.obtainStyledAttributes(attrs, R.styleable.EventLayout);
this.columnStart = a.getInt(R.styleable.EventLayout_event_columnStart, 0);
this.columnEnd = a.getInt(R.styleable.EventLayout_event_columnEnd, this.columnStart + 1);
this.rowStart = a.getInt(R.styleable.EventLayout_event_rowStart, 0);
this.rowEnd = a.getInt(R.styleable.EventLayout_event_rowEnd, this.rowStart + 1);
a.recycle();
}
public int getWidth() {
return columnEnd - columnStart;
}
public int getHeight() {
return rowEnd - rowStart;
}
}
And this is the event-layout - event.xml:
<?xml version="1.0" encoding="utf-8"?>
<LinearLayout
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_centerInParent="true"
android:orientation="vertical"
android:gravity="center" >
<TextView android:id="#+id/text_event_name"
style="#style/Event_TextView1" />
<TextView android:id="#+id/text_event_name2"
style="#style/Event_TextView2" />
</LinearLayout>
<TextView android:id="#+id/text_event_weeks"
style="#style/Event_TextView2"
android:layout_alignParentBottom="true"
android:layout_alignParentLeft="true" />
<TextView android:id="#+id/text_event_room"
style="#style/Event_TextView2"
android:layout_alignParentBottom="true"
android:layout_alignParentRight="true" />
In my Activity I've got that code:
Schedule schedule = new Schedule(this, 4, rowTimes.length, 15, 15, rowTimes);
Cursor cursor = dbManager.getEvents(day);
MySimpleCurserAdapter adapter = ... ??
// schedule.setAdapter not working...
How can I add events to the schedule with the data from the cursor?
You should not need to be extending ListView. You just want to add an instance of ListView to your layout.
It sounds like you might want to be using a SimpleCursorAdaptor, where you can map items in your custom view to the data model objects you want them to display.
See Binding to Data with Adapter and Hello ListView for some examples of the right ways to use adapters and ListViews.

android listview display all available items without scroll with static header

I'm having a little difficulties while trying to get a certain layout to work: I want to have list. List does not have to be scrollable, but should be shown completely. But the page itself should be able to scroll (with the lists in it), if the total content ist higher than the screen.
<ScrollView
xmlns:android="http://schemas.android.com/apk/res/android"
android:layout_width="fill_parent"
android:layout_height="wrap_content"
>
<LinearLayout
xmlns:android="http://schemas.android.com/apk/res/android"
android:id="#+id/linear_layout"
android:orientation="vertical"
android:layout_width="fill_parent"
android:layout_height="wrap_content"
android:layout_weight="1"
android:background="#ff181818"
>
<Textview android:id="#+id/my_text" text="header contents goes here" android:layout_width="fill_parent" android:layout_height="wrap_content"/>
<Textview android:id="#+id/headertext" text="header contents goes here" android:layout_width="fill_parent" android:layout_height="wrap_content"/>
<ListView
android:id="#+id/my_list1"
android:layout_height="wrap_content"
android:layout_width="fill_parent"
/>
</LinearLayout>
</ScrollView>
it only uses a small part of the screen (about 2 lines per list), instead of filling the available height, and the lists themselves can be scrolled. How can I change the layout to always show the whole lists but have the screen be scrollalbe?
The solution I used is to replace ListView with LinearLayout. You can create all your items inside LinearLayout, they will all be displayed. So there's really no need to use ListView.
LinearLayout list = (LinearLayout)findViewById(R.id.list_recycled_parts);
for (int i=0; i<products.size(); i++) {
Product product = products.get(i);
View vi = inflater.inflate(R.layout.product_item, null);
list.addView(vi);
}
As #Alex noted in the accepted answer that LinearLayout is hardly a replacement. I had a problem where LinearLayout was not an option, that's when i came across this blog. I will put the code here for reference purposes. Hope it helps someone out there!
public class UIUtils {
/**
* Sets ListView height dynamically based on the height of the items.
*
* #param listView to be resized
* #return true if the listView is successfully resized, false otherwise
*/
public static boolean setListViewHeightBasedOnItems(ListView listView) {
ListAdapter listAdapter = listView.getAdapter();
if (listAdapter != null) {
int numberOfItems = listAdapter.getCount();
// Get total height of all items.
int totalItemsHeight = 0;
for (int itemPos = 0; itemPos < numberOfItems; itemPos++) {
View item = listAdapter.getView(itemPos, null, listView);
item.measure(0, 0);
totalItemsHeight += item.getMeasuredHeight();
}
// Get total height of all item dividers.
int totalDividersHeight = listView.getDividerHeight() *
(numberOfItems - 1);
// Set list height.
ViewGroup.LayoutParams params = listView.getLayoutParams();
params.height = totalItemsHeight + totalDividersHeight;
listView.setLayoutParams(params);
listView.requestLayout();
return true;
} else {
return false;
}
}
}
Usage:
//initializing the adapter
listView.setAdapter(adapter);
UIUtils.setListViewHeightBasedOnItems(listView);
//whenever the data changes
adapter.notifyDataSetChanged();
UIUtils.setListViewHeightBasedOnItems(listView);
You can make your own customlistview. (It can extends ListView/ExpandableListView/GridView) and override the onMeasure method with this. With this you'll never need to call a function or anything. Just use it in your xml.
#Override
public void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
int expandSpec = MeasureSpec.makeMeasureSpec(Integer.MAX_VALUE >> 2,
MeasureSpec.AT_MOST);
super.onMeasure(widthMeasureSpec, expandSpec);
}
I had a ListView in my layout and wanted to use a library which can't handle a ListView here because it wraps it into a ScrollView. The best solution for me is based on FedorĀ“s answer.
Since I already got an ArrayAdapter for the ListView I wanted to re-use it:
LinearLayout listViewReplacement = (LinearLayout) findViewById(R.id.listViewReplacement);
NamesRowItemAdapter adapter = new NamesRowItemAdapter(this, namesInList);
for (int i = 0; i < adapter.getCount(); i++) {
View view = adapter.getView(i, null, listViewReplacement);
listViewReplacement.addView(view);
}
For me this works fine because I just need to display dynamic data varying from 1 to 5 elements. I just had to add my own divider.
If someone still has the problem then you can make customList and add onMesure() method just like I implemented it:
public class ScrolleDisabledListView extends ListView {
private int mPosition;
public ScrolleDisabledListView(Context context) {
super(context);
}
public ScrolleDisabledListView(Context context, AttributeSet attrs) {
super(context, attrs);
}
public ScrolleDisabledListView(Context context, AttributeSet attrs, int defStyle) {
super(context, attrs, defStyle);
}
#Override
public boolean dispatchTouchEvent(MotionEvent ev) {
final int actionMasked = ev.getActionMasked() & MotionEvent.ACTION_MASK;
if (actionMasked == MotionEvent.ACTION_DOWN) {
// Record the position the list the touch landed on
mPosition = pointToPosition((int) ev.getX(), (int) ev.getY());
return super.dispatchTouchEvent(ev);
}
if (actionMasked == MotionEvent.ACTION_MOVE) {
// Ignore move events
return true;
}
if (actionMasked == MotionEvent.ACTION_UP) {
// Check if we are still within the same view
if (pointToPosition((int) ev.getX(), (int) ev.getY()) == mPosition) {
super.dispatchTouchEvent(ev);
} else {
// Clear pressed state, cancel the action
setPressed(false);
invalidate();
return true;
}
}
return super.dispatchTouchEvent(ev);
}
#Override
public void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
int expandSpec = MeasureSpec.makeMeasureSpec(Integer.MAX_VALUE >> 2,
MeasureSpec.AT_MOST);
super.onMeasure(widthMeasureSpec, expandSpec);
}
}
Check this out:
ListView ignoring wrap_content
Using android:layout_height and android:layout_weight solved it for me:
<ListView
android:layout_height="0dp"
android:layout_weight="1"
/>
I just did it using setting params of ListView
public static void setListViewHeightBasedOnChildren(ListView listView) {
//this comes from value from xml tag of each item
final int HEIGHT_LARGE=75;
final int HEIGHT_LARGE=50;
final int HEIGHT_LARGE=35;
ViewGroup.LayoutParams params = listView.getLayoutParams();
int screenSize = getResources().getConfiguration().screenLayout & Configuration.SCREENLAYOUT_SIZE_MASK;
switch(screenSize) {
case Configuration.SCREENLAYOUT_SIZE_LARGE:
params.height =(int) (HEIGHT_LARGE*size);
break;
case Configuration.SCREENLAYOUT_SIZE_NORMAL:
params.height =(int) (HEIGHT_NORMAL*size);
break;
case Configuration.SCREENLAYOUT_SIZE_SMALL:
params.height =(int) (HEIGHT_SMALL*size);
break;
}
listView.setLayoutParams(params);
}
I don't have a static header, but using HussoM's post as a clue, here is what I was able to get to work. In my scenario, the height of the items in the list was non-uniform, due to variable text sentences in each of the items, and I am using wrap_content for the height and match_parent for the width.
public class NonScrollableListView extends ListView {
public NonScrollableListView(Context context) {
super(context);
}
public NonScrollableListView(Context context, AttributeSet attrs) {
super(context, attrs);
}
public NonScrollableListView(Context context, AttributeSet attrs, int defStyleAttr) {
super(context, attrs, defStyleAttr);
}
public NonScrollableListView(Context context, AttributeSet attrs, int defStyleAttr, int defStyleRes) {
super(context, attrs, defStyleAttr, defStyleRes);
}
/**
* Measure the height of all the items in the list and set that to be the height of this
* view, so it appears as full size and doesn't need to scroll.
* #param widthMeasureSpec
* #param heightMeasureSpec
*/
#Override
public void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
ListAdapter adapter = this.getAdapter();
if (adapter == null) {
// we don't have an adapter yet, so probably initializing.
super.onMeasure(widthMeasureSpec, heightMeasureSpec);
return;
}
int totalHeight = 0;
// compute the height of all the items
int itemCount = adapter.getCount();
for (int index=0; index<itemCount; index++) {
View item = adapter.getView(index, null, this);
// set the width so it can figure out the height
item.measure(widthMeasureSpec, 0);
totalHeight += item.getMeasuredHeight();
}
// add any dividers to the height
if (this.getDividerHeight() > 0) {
totalHeight += this.getDividerHeight() * Math.max(0, itemCount - 1);
}
// make it so
this.setMeasuredDimension(widthMeasureSpec,
MeasureSpec.makeMeasureSpec(totalHeight, MeasureSpec.EXACTLY));
}
}
If all items has the same height
int totalItemsHeight = baseDictionaries.size() * item.getMeasuredHeight();
int totalDividersHeight = listView.getDividerHeight() * (baseDictionaries.size() - 1);
int totalPadding = listView.getPaddingBottom() + listView.getPaddingTop();
LinearLayout.LayoutParams lp = (LinearLayout.LayoutParams) listTranslationWords.getLayoutParams();
lp.height = totalItemsHeight + totalDividersHeight + totalPadding;
listTranslationWords.setLayoutParams(lp);
Iam supprised no one see this.U cant have two scrolls on the same layout. 1st u have a scrollview and then u have a list, i bet u are killing some android good practices there.
If you want a simple solution to this problem without extending ListView class, this is a solution for you.
mListView.post(new Runnable() {
#Override
public void run() {
int height = 0;
for(int i = 0; i < mListView.getChildCount();i++)
height += mListView.getChildAt(i).getHeight();
ViewGroup.LayoutParams lParams = mListView.getLayoutParams();
lParams.height = height;
mListView.setLayoutParams(lParams);
}
});
In my case, I had ListView inside ScrollView and scrollview was shrinking listview by default. So I just add this in my ScrollView and it worked for me
android:fillViewport="true"
Set android:layout_height="fill_parent" in your LinearLayout

Categories

Resources