Create Android subviews like iOS subviews - android

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.

Related

Programmatically change one attribute on custom style

I am creating several textviews that all use the same style. I am attempting to use a SeekBar to update the textsize within the Style so it applies to all textviews with a minimal amount of code. I know I can use a SeekBar to set the textsize of the textviews individually but that seems like a lot of work. The problem is that everywhere I look all I find is that you cannot change the style. Is there any other work around besides doing code like below:
Define my textviews
TextView tv1 = (TextView)findViewById(R.id.tv1);
TextView tv2 = (TextView)findViewById(R.id.tv2);
TextView tv3 = (TextView)findViewById(R.id.tv3);
Inside my SeekBar
progress = seekBarProgress;
if(progress == 0)
{
tv1.setTextSize(12);
tv2.setTextSize(12);
tv3.setTextSize(12);
}
if(progress == 1)
{
tv1.setTextSize(14);
tv2.setTextSize(14);
tv3.setTextSize(14);
}
Etc etc..
I would like to be able to change one attribute of a custom style. I cannot change it all together to a different custom style because I am going to do SeekBars for Text size, text color, background color, etc. If I did custom styles for each one there would be TONS.
Since I will have a lot of textviews doing this method seems illogical. Is there a better way? Thanks.
GOT THE ANSWER!
Instead of changing the style I retrieve the child and then the child of that child and change it accordingly like below.
LinearLayout masterLayout = (LinearLayout)findViewById(R.id.masterLayout);
int childCount = masterLayout.getChildCount();
for(int i = 0; i < childCount; i++)
{
LinearLayout innerChild = ((LinearLayout)masterLayout.getChildAt(i));
int childOfChildCount = innerChild.getChildCount();
for(int x = 0; x < childOfChildCount; x++)
{
((TextView)innerChild.getChildAt(x)).setTextSize(30);
}
}
What about group these TextView in only one Layout? Then change it programmatically.
In my example I group all of TextViews in only one LinearLayout.
LinearLayout ll = (LinearLayout) findViewById(R.id.layout);
int childCount = ll.getChildCount();
for (int i=0; i<childCount; i++){
((TextView)ll.getChildAt(i)).setTextSize(20);
}
Be sure that you only have TextViews in your layout.
I know that you have already implemented and accepted a solution, however, I have been thinking about this for a while for myself, and have come up with an alternative, more generic solution which may be of use. This involves four elements
Creating an interface for the style changed events
Creating a handler for the style changed events
Extending TextView to have one or more style changed events
Triggering the style change events
Although this is more code it has the advantages of being independent of layouts, and of the view classes (ie the same handler can be used for different View Classes if you also wanted to change the font size of Buttons, EditTexts etc).
The example below just implements a text size change, but the same technique could be used to implement any other style changes.
The Interface
public interface StyleChange {
void onTextSizeChanged(float size);
}
The Handler
public class TextStyleHandler {
private static TextStyleHandler instance;
private LinkedList<StyleChange> listeners = new LinkedList<>();
public static TextStyleHandler getInstance() {
if (instance == null) instance = new TextStyleHandler();
return instance;
}
public void register(StyleChange item) {
listeners.add(item);
}
public void unregister(StyleChange item) {
listeners.remove(item);
}
public void setTextSize(float f) {
for (StyleChange listener:listeners)
listener.onTextSizeChanged(f);
}
}
The Extended TextView
public class StyledTextView extends TextView implements StyleChange {
public StyledTextView(Context cx) {
super(cx);
init();
}
public StyledTextView(Context cx, AttributeSet attrs) {
super(cx, attrs);
init()
}
public StyledTextView(Context cx, AttributeSet attrs, int defStyle) {
super(cx, attrs, defStyle);
init();
}
private void init() {
// Any other setup here (eg setting the default size
// or getting current value from shared preferences)
TextStyleHandler.getInstance().register(this);
}
public void onTextSizeChanged(float size) {
setTextSize(size);
}
#Override
protected void onDetachedFromWindow() {
super.onDetachedFromWindow();
TextStyleHandler.getInstance().unregister(this);
}
}
Triggering the style change event
This can be done from your activity, and will change the style of all registered views
TextStyleHandler.getInstance().setTextSize(size);

Android CollapsingToolbarLayout with custom View

I'm following the Cheesesquare example project to understand the new design material library.
I'm wondering if there's a way to use a custom view (like Telegram) with ImageView, title and subtitle instead of the simple Title provided by CollapsingToolbarLayout widget.
Thanks.
I had the same problem and spend many hours trying to find a solution. My solution was to add the collapsing Views (ImageView and TextView) inside the CollapsingToolbarLayout and then handle the transition in code. This way it's more flexible and simpler than extending from CollapsingToolbarLayout.
First you'll need to add your Views inside the CollapsingToolbarLayout with the parallax properties:
<ImageView
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:paddingTop:"80dp"
android:src="#drawable/icon"
app:layout_collapseMode="parallax"
app:layout_collapseParallaxMultiplier="0.8"/> //set vertical transition here
Then set the scaling of the Views with the help of an OnOffsetchangeListner:
private static final float SCALE_MINIMUM=0.5f;
appBarLayout.setOnWorkingOffsetChange(new ControllableAppBarLayout.OnWorkingOffsetChange() {
#Override
public void onOffsetChange(int offSet, float collapseDistance) {
imageView.setScaleX(1 + (collapseDistance * SCALE_MINIMUM));
imageView.setScaleY(1 + (collapseDistance * SCALE_MINIMUM));
textView.setScaleX(1 + (collapseDistance * SCALE_MINIMUM));
textView.setScaleY(1 + (collapseDistance * SCALE_MINIMUM));
// You can also setTransitionY/X, setAlpha, setColor etc.
}
});
Somehow the default offsetChangedListener didn't work properly for me (you probably still should try it with the default listener first), so I used the ControllableAppBarLayout from https://gist.github.com/blipinsk/3f8fb37209de6d3eea99 and added the following:
private OnWorkingOffsetChange onWorkingOffsetChange;
#Override
public void onOffsetChanged(AppBarLayout appBarLayout, int i) {
if (!isInEditMode()) {
onWorkingOffsetChange.onOffsetChange(i, (float) i / appBarLayout.getTotalScrollRange());
}
}
public void setOnWorkingOffsetChange(OnWorkingOffsetChange listener) {
this.onWorkingOffsetChange = listener;
}
public interface OnWorkingOffsetChange {
void onOffsetChange(int offSet, float collapseDistance);
}
The only problem is, that you would need to set
app:contentScrim="#00000000" (transparent)
for your CollapsingToolbarLayout, so your views are still visible when the toolbar is collapsed. If you really need the collapsing-background effect I'm sure you could "fake" this by setting the alpha of a background ImageView in the OffsetChangeListener. ;)
From the widget itself there doesn't seem to be a way to enable this directly, like it was possible to add custom views to the Toolbar.
What you could try to do however, is open the source of the CollapsingToolbarLayout.class and check out how the CollapsingTextHelper.class is used to have the title set. You could try to make your own widget by extending from the the CollapsingToolbarLayout.
These links can help you out with creating custom components/views, if you haven't created them before:
Custom Views, Custom Components
I haven't tried this yet, but it's actually something I was thinking about trying to achieve a similar solution as you are looking for. Steps I tihkn I would follow, so far:
Create custom attributes for subtitle settings in attrs.xml
Create your own MyCollapsingToolbarLayout by extending the original one.
Make sure to call super in the constructors, so the original component will stay intact.
Create a subtitleTextHelper by adding a new CollapsingTextHelper to your component.
Override onDraw to actually draw your subtitle.
Update the layout containing your CollapingsToolbarLayout with your subtitle attributes (default styling and such, maybe a fixed subtitle Text).
Apply the changes in the Activity containing your CollapsingToolbar. (Convert CollapsingToolbarlayout to MyCollapingsToolbarLayout, set subtitles, extra custom settings, etc).
Cross fingers, test.
Going to have a look at it now.
Going to edit Christopher's answer slightly to show how you can get your custom view to not disappear on collapse:
First you'll need to add your Views inside the CollapsingToolbarLayout with the parallax properties:
<ImageView
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:paddingTop:"80dp"
android:src="#drawable/icon"
app:layout_collapseMode="parallax"
app:layout_collapseParallaxMultiplier="0.8"/> //set vertical transition here
Instead add the custom view's programmatically and it won't disappear on collapse. For example here is a view that contains a title and a subtitle:
final FrameLayout frameLayout = new FrameLayout(mActivity);
FrameLayout.LayoutParams frameLayoutParams = new FrameLayout.LayoutParams(FrameLayout.LayoutParams.MATCH_PARENT,
FrameLayout.LayoutParams.MATCH_PARENT);
frameLayout.setLayoutParams(frameLayoutParams);
// Create new LinearLayout
final LinearLayout linearLayout = new LinearLayout(mActivity);
frameLayoutParams =new FrameLayout.LayoutParams(FrameLayout.LayoutParams.MATCH_PARENT, dpToPixels(78));
frameLayoutParams.gravity = Gravity.BOTTOM;
linearLayout.setLayoutParams(frameLayoutParams);
linearLayout.setOrientation(LinearLayout.VERTICAL);
// Add textviews
final TextView textView1 = new TextView(mActivity);
LinearLayout.LayoutParams linearLayoutParams =new LinearLayout.LayoutParams(LinearLayout.LayoutParams.WRAP_CONTENT,
LinearLayout.LayoutParams.WRAP_CONTENT);
frameLayoutParams.gravity = Gravity.BOTTOM;
textView1.setLayoutParams(linearLayoutParams);
textView1.setText("Title");
textView1.setTextColor(ContextCompat.getColor(mActivity, R.color.colorWhite));
textView1.setTextSize(TypedValue.COMPLEX_UNIT_SP, 40);
linearLayout.addView(textView1);
final TextView textView2 = new TextView(mActivity);
linearLayoutParams = new LinearLayout.LayoutParams(LinearLayout.LayoutParams.WRAP_CONTENT,
LinearLayout.LayoutParams.WRAP_CONTENT);
textView2.setLayoutParams(linearLayoutParams);
textView2.setText("Subtitle");
textView2.setTextColor(ContextCompat.getColor(mActivity, R.color.colorWhite));
textView2.setTextSize(TypedValue.COMPLEX_UNIT_SP, 20);
linearLayout.addView(textView2);
frameLayout.addView(linearLayout);
collapsingToolbar.addView(frameLayout);
final float SCALE_MIN=0.4f;
AppBarLayout appBarLayout = (AppBarLayout) mActivity.findViewById(R.id.appBarLayout);
appBarLayout.addOnOffsetChangedListener(new AppBarLayout.OnOffsetChangedListener() {
#Override
public void onOffsetChanged(AppBarLayout appBarLayout, int offSet) {
float collapsedRatio = (float) offSet / appBarLayout.getTotalScrollRange();
linearLayout.setScaleX(1 + (collapsedRatio * SCALE_MIN));
linearLayout.setScaleY(1 + (collapsedRatio * SCALE_MIN));
FrameLayout.LayoutParams frameLayoutParams =new FrameLayout.LayoutParams(FrameLayout.LayoutParams.MATCH_PARENT, dpToPixels(78));
frameLayoutParams.gravity = Gravity.BOTTOM;
frameLayoutParams.setMargins(Math.round(dpToPixels(48) * (1+collapsedRatio)), 0, 0, Math.round(dpToPixels(15) * collapsedRatio));
linearLayout.setLayoutParams(frameLayoutParams);
// You can also setTransitionY/X, setAlpha, setColor etc.
}
});
/////
float lastCollapsedRatio = -2;
////
private int dpToPixels(int padding_in_dp){
final float scale = getResources().getDisplayMetrics().density;
int padding_in_px = (int) (padding_in_dp * scale + 0.5f);
return padding_in_px;
}

how to set textview dynamically according to numbers of strings

I am getting list of phone companies from web service and i have to set it to textview but the problem is i am not getting alignment as above image.How to achieve it.
From what I understand, you want to add text views one beside the other, but when they overflow (go out of the screen) the next text view should be placed in the next line.
Doing this is not trivial. Implementing something like this (optimally and correctly) requires understanding of how android draws views (onMeasure and onLayout). However if you do not care about efficiency that much (mainly because you are going to do it only for a small portion of the view) then here is my quick hack:
mContainer = (RelativeLayout) findViewById(R.id.container);
// first layout all the text views in a relative layout without any params set.
// this will let the system draw them independent of one another and calculate the
// width of each text view for us.
for (int i = 0; i < 10; i++) {
TextView tv = new TextView(getApplicationContext());
tv.setText("Text View " + i);
tv.setId(i+1);
tv.setPadding(10, 10, 20, 10);
mContainer.addView(tv);
}
// post a runnable on the layout which will do the layout again, but this time
// using the width of the individual text views, it will place them in correct position.
mContainer.post(new Runnable() {
#Override
public void run() {
int totalWidth = mContainer.getWidth();
// loop through each text view, and set its layout params
for (int i = 0; i < 10; i++) {
View child = mContainer.getChildAt(i);
RelativeLayout.LayoutParams params = new RelativeLayout.LayoutParams(ViewGroup.LayoutParams.WRAP_CONTENT, ViewGroup.LayoutParams.WRAP_CONTENT);
// this text view can fit in the same row so lets place it relative to the previous one.
if(child.getWidth() < totalWidth) {
if(i > 0) { // i == 0 is in correct position
params.addRule(RelativeLayout.RIGHT_OF, mContainer.getChildAt(i-1).getId());
params.addRule(RelativeLayout.ALIGN_BOTTOM, mContainer.getChildAt(i-1).getId());
}
}
else {
// place it in the next row.
totalWidth = mContainer.getWidth();
params.addRule(RelativeLayout.ALIGN_PARENT_LEFT);
params.addRule(RelativeLayout.BELOW, mContainer.getChildAt(i-1).getId());
}
child.setLayoutParams(params);
totalWidth = totalWidth - child.getWidth();
}
mContainer.requestLayout();
}
});
Basically, I let the system do the layout and measurement for me in the first round(s) of drawing. Then using the widths of each text view now available, I reset the layout params based on the wrapping logic and do the layout again.
Try it with text of different size, it will auto adjust. I would say this solution is pretty hacky but it works. If you are not satisfied with it take a look at this.
use
android:textAlignment="textStart"

Setting ImageViews in RelativeLayout in API Version 8

I have created a basic RelativeLayout in my XML file. In my code, I want to dynamically create several ImageViews and place them at different locations within the RelativeLayout. Everything I've tried (ImageView.setX(), ImageView.setTranslationX(), ImageView.setPadding()) either says I need a higher API level (11+) or causes the ImageView to not appear.
If I do not try to do anything with the location of the ImageView, then the image does appear on the screen in the (0,0) position.
This simple app will layout 15 icons in three rows dynamically using RelativeLayout. There is no reason to use AbsoluteLayout - it is also deprecated.
The main activity.
public class MainActivity extends Activity {
private int mWidth;
private int mTile;
private int mColMax = 5;
private Context mContext;
#Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
mContext = this;
// the screen width is need to work out the tile size
mWidth = mContext.getResources().getDisplayMetrics().widthPixels;
// how wide (and high) each icon will be to fit the screen.
mTile = (mWidth / mColMax);
setContentView(R.layout.activity_main);
// layout the icons
initUI();
}
/**
* Layout 15 icon images in three rows dynamically.
*/
private void initUI() {
// this is the layout from the XML
ViewGroup layout = (ViewGroup) findViewById(R.id.main_layout);
ImageView iv;
RelativeLayout.LayoutParams params;
int i = 0;
int row = 0;
int col = 0;
do {
params = new RelativeLayout.LayoutParams(mTile,mTile);
params.setMargins((col * mTile), (row * mTile), 0, 0);
iv = new ImageView(mContext);
iv.setAdjustViewBounds(true);
iv.setScaleType(ScaleType.FIT_CENTER);
iv.setImageResource(R.drawable.ic_launcher);
iv.setLayoutParams(params);
layout.addView(iv);
if (col == mColMax) {
row++;
col = 0;
} else {
col++;
}
} while (++i <= 16);
}
}
And the layout XML.
<RelativeLayout
xmlns:android="http://schemas.android.com/apk/res/android"
android:id="#+id/main_layout"
android:layout_width="match_parent"
android:layout_height="match_parent">
</RelativeLayout>
RelativeLayouts are used to place items in relationship to other items. YOu don't use them to place layouts at specific positions like a setX. If you want to place the new item relative to existing items, look at RelativeLayout.LayoutParams- you can set layout_alignXXX and layout_toXXXOf type parameters through them.
If you need an exact pixel position, use the deprecated AbsoluteLayout. Just be aware its going to look ugly as heck on any device with a different aspect ratio or size screen without a ton of work.

Expanding in Context in a ListView - Android

I have a listView, one component of the row is a TextView. By default, I wish to show only 2 rows of text in the TextView. But if the user taps on the TextView I wanted to the textView to expand in context.
I actually have this portion working, but I want to have a more content indicator :
My current implementation (Below) has it's own issues w/ not collapsing if the list view is scrolled, but I will handle that by storing the values for each cursor record in some collection..
I tried using chatBubble.getLineCount() but that returns zero initially b/c it has not gone through onLayout (from my understanding).
I only wish to show it if there is more than 2 lines of content.
I figure my solution will be creating my own implementation of TextView which can handle some of my requirements, but not sure if anyone has any examples I can look at.. (Or other suggestions).
<RelativeLayout
android:id="#+id/linear_layout_row_three"
android:layout_width="fill_parent"
android:layout_height="wrap_content"
android:layout_below="#+id/linear_layout_row_two"
android:layout_toRightOf="#+id/munzeeQuickContact"
android:orientation="horizontal" >
<TextView
android:id="#+id/chat_bubble"
android:clickable="true"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:maxLines="2"
android:background="#drawable/chat_bubble"
android:text="I went looking for this particular token on a rainy day, and I was unable to locate it due to the bad weather, next time please leave an um I went looking for this particular munzee on a rainy day, and I was unable to locate it due to the bad weather, next time please leave an um" />
<ImageView
android:id="#+id/minimize_maximize"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_alignBottom="#id/chat_bubble"
android:layout_alignRight="#id/chat_bubble"
android:visibility="gone"
android:src="#android:drawable/ic_menu_more"/>
</RelativeLayout>
Here is some of the source I currently have :
final TextView chatBubble = (TextView) view.getTag(R.id.chat_bubble);
final ViewGroup expandableContainer = (ViewGroup) view.getTag(R.id.linear_layout_row_three);
final ImageView minimizeMaximize = (ImageView) view.getTag(R.id.minimize_maximize);
chatBubble.setOnClickListener(
new View.OnClickListener() {
boolean isExpanded = false;
int lastHeight = 0;
// This is for the auto expanding text view
#Override
public void onClick(View v) {
if (isExpanded) {
ViewGroup.LayoutParams params = (ViewGroup.LayoutParams) expandableContainer
.getLayoutParams();
params.height = lastHeight;
chatBubble.setMaxLines(2);
expandableContainer.setLayoutParams(params);
expandableContainer.invalidate();
minimizeMaximize.setVisibility(View.VISIBLE);
} else {
lastHeight = expandableContainer.getHeight();
ViewGroup.LayoutParams params = (ViewGroup.LayoutParams) expandableContainer
.getLayoutParams();
params.height = ViewGroup.LayoutParams.WRAP_CONTENT;
chatBubble.setMaxLines(99);
expandableContainer.setLayoutParams(params);
expandableContainer.invalidate();
minimizeMaximize.setVisibility(View.VISIBLE);
}
isExpanded = !isExpanded;
}
});
I figure my solution will be creating my own implementation of
TextView which can handle some of my requirements, but not sure if
anyone has any examples I can look at..
Have a look at the class below:
public class LimitedTextView extends TextView {
private boolean mStatus;
public LimitedTextView(Context context, AttributeSet attrs) {
super(context, attrs);
}
#Override
protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
super.onMeasure(widthMeasureSpec, heightMeasureSpec);
Paint p = getPaint();
String s = getText().toString();
if (s != null && !s.equals("")) {
int m = (int) p.measureText(s);
if (m < getMeasuredWidth() * 2) {
modifyParent(true);
} else {
modifyParent(false);
}
}
}
private void modifyParent(boolean how) {
RelativeLayout rl = (RelativeLayout) getParent();
rl.findViewById(R.id.minimize_maximize).setVisibility(
how ? View.GONE : View.VISIBLE);
if (mStatus) {
setMaxLines(40); // arbitrary number, set it as high as possible
} else {
setMaxLines(2);
}
}
public void storeCurrentStatus(boolean status) {
mStatus = status;
}
}
The LimitedTextView will measure its text using its own Paint object and test it against the measured width. If it fits on the two allowed rows it will hide the ImageView, otherwise it will show it. It also stores the current status of row(expanded/not-expanded) and increases or decreases the maximum number of lines to obtain the proper appearance.
In the getView method of the adapter you would:
set the text
set the status from a boolean array according to a position(this is also required to keep the rows in order as you scroll the list):
textView.storeCurrentStatus(mStatus[position])
set the OnClickListener on the LimitedTextView itself and from there update the status:
mStatus[(Integer) v.getTag()] = !mStatus[(Integer) v.getTag()];
notifyDataSetChanged();
based on the same mStatus boolean array you'll probably change the drawable of the ImageView, to show a different one depending on if the TextView is expanded or not
I manually wrote it, so there could be some mistakes I'm missing right now, take it as an idea. The LimitedTextView could be improved as in performance, I also don't know how well it would behave if you want to animate expanding the text.

Categories

Resources