I want to do the Dashboard pattern. I currently do this for each home button:
<FrameLayout
android:layout_weight="1"
android:focusable="true"
android:onClick="onHomeButtonClicked"
android:background="#drawable/button_background">
<TextView
android:text="#string/button_text"
android:drawableTop="#drawable/button_icon"
android:layout_gravity="center"
android:gravity="center"
android:background="#android:color/transparent" />
</FrameLayout>
The reason I wrap my button inside a FrameLayout is I want to:
Maximize the clickable area
Make the icon and text properly
centered.
I tried doing this in the past but gave up because I couldn't figure out a screensize-independent way of centering the text and icon:
<Button
android:layout_weight="1"
android:background="#drawable/button_background"
android:text="#string/button_text"
android:onClick="onHomeButtonClicked"
android:drawableTop="#drawable/button_icon"
android:drawablePadding="DONT_KNOW_WHAT_TO_PUT_IN_HERE" />
My question: Is it possible to do all these:
Not wrap the Button inside other
layout (using only 1 view per
button)
Maximize the clickable area
Properly center the icon and text in
a screensize-independent way
Many thanks.
Here's some code that I use to build a dashboard programmatically:
#Override public View getView(int position, View convertView,
ViewGroup parent) {
TextView v = (TextView) convertView;
if (v == null) {
v = new TextView(DashboardActivity.this);
v.setGravity(Gravity.CENTER);
}
v.setCompoundDrawablesWithIntrinsicBounds(0,
mIcons[position].drawableId, 0, 0);
v.setText(mIcons[position].labelId);
return v;
}
You could instead use android:drawableTop in an XML file if you could build the dashboard beforehand.
Related
I'm getting frustrated about this:
When I define a custom ListView Layout,
Android Studio doesn't keep the background drawable I set in there.
Tried many things, and setting background programmatically doesn't work
since it's ignoring the layout_width which must be set to "wrap_content".
Actual style of background
Result without coding
If anyone could help me, I'd be very grateful !:)
EDIT:
I'm creating a Messenger and I want to display messages in a similar way to WhatsApp, where messages are shown in a listView. Depending on message is sent or received, items should be aligned ParentStart or ParentEnd.
But more importantly, if a message only contains a few chars, I don't want the ListItem Background to fill the entire screen, so it should be set dynamically.
I thought I could achieve this through simply setting wrap content in the parent layout file.
Files look like this:
<?xml version="1.0" encoding="utf-8"?>
<RelativeLayout
xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:tools="http://schemas.android.com/tools"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:orientation="vertical"
android:layout_alignParentEnd="true">
<LinearLayout
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_alignParentEnd="true"
android:layout_alignParentTop="true"
android:layout_marginBottom="4dp"
android:layout_marginTop="4dp"
tools:background="#drawable/background_message_sent">
//Here are TextViews
</LinearLayout>
</RelativeLayout>
Here is my ListViewAdapter, where I set background
(#drawable/background_message_sent/received) programmatically.
However, this covers the entire width of ListView, regardless of message length.
#Override
public View getView(int pos, View convertView, ViewGroup parent) {
int currentUserID = 1;
int senderID = messagesArrayList.get(pos).getSenderID();
if (senderID == currentUserID){
View v = View.inflate(context, R.layout.layout_chat_message_sent, null);
v.setBackgroundResource(R.drawable.background_message_sent);
TextView tvMessageText = v.findViewById(R.id.tvMessageText);
TextView tvTimeStamp = v.findViewById(R.id.tvTimeStamp);
tvMessageText.setText(messagesArrayList.get(pos).getMessageText());
tvTimeStamp.setText(messagesArrayList.get(pos).getTimeStamp());
return v;
}
else {
View v = View.inflate(context, R.layout.layout_chat_message_received, null);
v.setBackgroundResource(R.drawable.background_message_received);
TextView tvMessageText = v.findViewById(R.id.tvMessageText);
TextView tvTimeStamp = v.findViewById(R.id.tvTimeStamp);
tvMessageText.setText(messagesArrayList.get(pos).getMessageText());
tvTimeStamp.setText(messagesArrayList.get(pos).getTimeStamp());
return v;
}
}
Well, after trying, I got the solution if anyone comes to this point:
You have to set your background drawable directly for each TextView, not for Parent Layouts.
These two Lines finally solved everything ^^
android:maxEms="14"
android:background="#drawable/background_message_sent"
<TextView
android:id="#+id/tvMessageText"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_marginBottom="5dp"
android:layout_marginEnd="0dp"
android:layout_marginTop="5dp"
android:layout_weight="0.22"
android:text="42456456456546"
android:textColor="#color/tentakelPrimary"
android:maxEms="14"
android:background="#drawable/background_message_sent"/>
So I am rather confused right now. I am using an XML layout so I can show an empty view like so:
<?xml version="1.0" encoding="utf-8"?>
<RelativeLayout xmlns:android="http://schemas.android.com/apk/res/android"
android:layout_width="fill_parent"
android:layout_height="fill_parent" >
<RelativeLayout
android:id="#android:id/empty"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_alignParentBottom="true"
android:layout_alignParentLeft="true"
android:layout_alignParentRight="true"
android:layout_alignParentTop="true" >
<TextView
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_centerInParent="true"
android:text="#string/samples_empty"
android:textAppearance="?android:attr/textAppearanceLarge" />
</RelativeLayout>
<ListView
android:id="#android:id/list"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_alignParentBottom="true"
android:layout_alignParentLeft="true"
android:layout_alignParentRight="true"
android:layout_alignParentTop="true" >
</ListView>
</RelativeLayout>
As you have probably seen a billion times here on so.
So I do setContentView(R.layout.foo) and it works the first time, but if I return to this Activity (As in onPause has been called and then onResume) I get this:
I call notifyDataSetChanged(); on the adapter and that works fine, what I don't get is why its being drawn twice?
Its not like I am creating a new ListView and then adding it to the view, I'm sure I would know about it if I was.
The getView method of the adapter:
#Override
public View getView(int position, View convertView, ViewGroup parent) {
RecordView av;
if(convertView == null){
av = new RecordView(mCtx, this, mRecords[position], position);
}else{
av = (RecordView) convertView;
av.setRecord(mRecords[position]);
}
return av;
}
This would be what it would look like normally...
NOTE
This doesn't seem to be happening every single time, and doesn't happen on a single event happening, but when it does, its when I return from another screen.
UPDATE
I noticed that when I had another activity on top (something that was transparent, like facebook chat heads, then I could see that the problem had occurred then, It doesn't seem to happen on onResume, but more likely on onPause which I actually don't do anything in.
You have this problem because you are dynamically create the row view each time while the convertview still has the old view existing and it is being reused. To get around this problem, you should give an id to every view (that is, every child view in your RecordView)when you dynamically create them, for example a child textview in your RecordView class should be instantiated like this
TextView tv = new TextView(this.getContext());
tv.setId(1);
...
Then, in your getView:
if(convertView == null){
av = new RecordView(mCtx, this, mRecords[position], position);
}else{
av = (RecordView) convertView;
av.findViewById(1).setText(mRecords[position]);
}
assuming your mRecords holds an array of String. If you have variant layout for different rows, you can provide a type to each view. See this for further details.
try changing android:layout_height="wrap_content" of listview to android:layout_height="fill_parent"
Look at the code :-
#Override
public View getView(int position, View convertView, ViewGroup parent) {
View row = convertView;
if (row == null) {
LayoutInflater inflater = ((Activity) mContext).getLayoutInflater();
row = inflater.inflate(R.layout.country_row, parent, false);
}
return row;
}
The country row XML is :--
<?xml version="1.0" encoding="utf-8"?>
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
android:layout_width="fill_parent"
android:layout_height="match_parent"
android:orientation="horizontal"
android:weightSum="1" >
<TextView
android:id="#+id/nameOfCountry"
android:layout_width="0dp"
android:layout_height="wrap_content"
android:layout_weight="0.8"
android:text="Country" />
<CheckBox
android:id="#+id/checkBox"
android:layout_width="0dp"
android:layout_height="wrap_content"
android:layout_weight="0.2"
android:gravity="right" />
Now my question is once I check few of the check boxes in the list view and the scroll the list view, many other check boxes are automatically checked....
I know there is problem in getView but I'm not able to figure out where....
Also the problem is solved if I don't reuse the convert view. But that is a dumb idea...
Any thoughts.....
That's because the ListView does recycling of views. Essentially, it reuses some views that go off screen to make the new ones on screen to help with performance. There are 2 ways of dealing with this:
Set the values of the views before you return the row. For example, you would set the nameOfCountry and whether or not the checkbox is checked before the return view line. To do this though, you need to keep track of what is checked.
Don't use the convertview and just inflate a new view every time. This may result in a performance hit, but as long as the list isn't too long it shouldn't be too noticable (at least in my experience). Simply igonre the convertview value
You have to set the state of the checkbox explicitly if you reuse an old View.
Something very strange is going on with a custom view containing a TextView. Roughly speaking, (relevant code below), I have a custom view (subclass of FrameLayout), which inflates some xml and adds the resulting View as a child. The inflated view contains a TextView, referred to below as "the label". Also possibly relevant (in case you're not going to look at the code) is that the custom view is added as a child to a MapView.
The following really weird stuff happens:
Do nothing: label is not visible, parent looks fine. Interestingly, the parent view is clearly taking the label's width into account (with the label visibility set to GONE the parent view is much narrower). In design mode the label is visible.
Touch the parent view: Label briefly becomes visible, then disappears. Holding your finger on it keeps the label visible until you end the touch.
Call this.setPressed(true), this of course being the parent view, from the parent view's constructor: Label becomes visible, and stays that way until the parent view is touched, after which behavior reverts to "normal".
Here's the relevant code. By way of context, we're sticking a balloon on a map when you touch the map, which should contain something along the lines of "Tap to choose this location". There are some other views in the balloon, but their visibility is set to GONE initially, and the code to fill them in and show them is disabled. There are no map overlays involved, just attaching the view directly to the map with MODE_MAP to stick it to a single location.
From the custom view, BalloonView.java. Nothing else in this class touches the layout/view methods.
public BalloonView(Context context) {
super(context);
if(!isInEditMode()){ // RoboGuice doesn't like edit mode
RoboGuice.getInjector(context).injectMembersWithoutViews(this);
}
LayoutInflater inflater = (LayoutInflater) context
.getSystemService(Context.LAYOUT_INFLATER_SERVICE);
// We're *not* attaching the view with it's default layout params.
View v = inflater.inflate(R.layout.balloon_view, this, false);
FrameLayout.LayoutParams params = new FrameLayout.LayoutParams(
ViewGroup.LayoutParams.MATCH_PARENT,
ViewGroup.LayoutParams.MATCH_PARENT);
params.gravity = Gravity.NO_GRAVITY;
addView(v,params);
label = (TextView) v.findViewById(R.id.label);
addressContainer = v.findViewById(R.id.addressContainer);
address1 = (TextView)findViewById(R.id.address1);
address2 = (TextView) findViewById(R.id.address2);
// makes label visible initially
// setPressed(true);
}
// This is the constructor that we actually call, in case it matters...
public BalloonView(Context context, OnClickListener onClick) {
this(context);
setOnClickListener(onClick);
}
balloon_view.xml
<TextView
android:id="#+id/label"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_marginBottom="6dip"
android:layout_marginLeft="6dip"
android:layout_marginRight="6dip"
android:layout_marginTop="6dip"
android:text="#string/map_balloon_label"
android:enabled="true"
android:textAppearance="?android:attr/textAppearanceMedium" />
<LinearLayout
android:id="#+id/addressContainer"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_marginBottom="6dip"
android:layout_marginLeft="8dip"
android:visibility="gone" >
<ImageView
android:layout_width="match_parent"
android:layout_height="match_parent"
android:contentDescription="#string/place"
android:src="#drawable/location_place" />
<LinearLayout
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_marginLeft="10dip"
android:orientation="vertical" >
<TextView
android:id="#+id/address1"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:textAppearance="?android:attr/textAppearanceSmall"
android:visibility="visible" />
<TextView
android:id="#+id/address2"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:textAppearance="?android:attr/textAppearanceSmall" />
</LinearLayout>
</LinearLayout>
</LinearLayout>
Here's where we attach the BalloonView to the MapView
private MapView.LayoutParams getBalloonViewLayoutParams(GeoPoint where){
return new MapView.LayoutParams(
ViewGroup.LayoutParams.WRAP_CONTENT,
ViewGroup.LayoutParams.WRAP_CONTENT,
where, MapView.LayoutParams.BOTTOM_CENTER);
}
private void showBalloon(QPGeoPoint where) {
latitude = where.getLatitude();
longitude = where.getLongitude();
BalloonView bv = getBalloonView();
if(bv.getParent() != mapView){
Ln.d("bv.getParent()!=mapView");
mapView.addView(balloonView, getBalloonViewLayoutParams(where));
}else{
Ln.d("parent was map view");
balloonView.setLayoutParams(getBalloonViewLayoutParams(where));
}
balloonView.setVisibility(View.VISIBLE);
balloonView.setLocation(where);
}
Other (possibly) relevant things:
We're using ActionBarSherlock, RoboGuice, and the roboguice-sherlock plugin.
The application theme is set to Sherlock.Theme.Light.DarkActionBar, but the activity in question has it's theme set to Theme.DeviceDefault.NoTitleBar.
I am completely baffled, have been trying to figure this out for hours, and will continue to do so and post updates as I find new clues.
Well ladies and gents, here it is:
The text and background were both white. Touching the text set it's color state to pressed, which made it black.
The quick fix is to change the textColor attribute on the text view.
A more general fix might be to use a different theme for this activity, say, Theme.DeviceDefault.Light.NoTitleBar.
Feeling pretty dumb on this one ;P
I have looked around, but most of them don't make any sense. Some have a third parameter that does not exists in the Android docs, at least anymore. Anybody have any idea how to accomplish this? I have this so far:
#Override
public View getView(int position, View convertView, ViewGroup parent) {
if(convertView == null) {
LayoutInflater inflater = LayoutInflater.from(parent.getContext());
convertView = inflater.inflate(R.layout.day_view_item, parent, false);
}
((TextView)convertView.findViewById(R.id.day_hour_side)).setText(array[position]);
if(count == 1) {
LinearLayout layout = (LinearLayout)convertView.findViewById(R.id.day_event_layout);
TextView create = new TextView(parent.getContext());
create.setLayoutParams(new LayoutParams(0, 30));
create.setBackgroundColor(Color.BLUE);
create.setText("Test");
layout.addView(create);
count = count -1;
}
return convertView;
}
I am trying to add it to a LinearLayout in a ListView, hence the method you see in the code. One problem is that the TextView is not showing up when I run it. So I was thinking I need the layout_weight. Though, I am confused about one thing. What values are the width and height parameters of the LayoutParams constructor (dp or px)? I will also add the xml of where I am trying to add it at incase that is helpful:
<?xml version="1.0" encoding="utf-8"?>
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:orientation="horizontal" >
<TextView
android:id="#+id/day_hour_side"
android:layout_width="wrap_content"
android:layout_height="62dp"
android:text="12AM"
android:background="#bebebe"
android:layout_weight="0"
android:textSize="10dp"
android:paddingLeft="5dp"
android:paddingRight="5dp"/>
<LinearLayout
android:id="#+id/day_event_layout"
android:layout_width="0dp"
android:layout_height="62dp"
android:layout_weight="1"
android:paddingTop="1dp"
android:paddingBottom="1dp"
android:orientation="horizontal" ></LinearLayout>
Firstly, the LayoutParams constructors use pixels (according to the documentation).
To set the layout_weight programmatically, you need to use LinearLayout.LayoutParams. ViewGroup.LayoutParams does not have a third argument, as you pointer out, but the former does.
Try this:
create.setLayoutParams(new LinearLayout.LayoutParams(0, 30, 1.0f));
ViewGroup.LayoutParams is a set of layout parameters for any View within any kind of layout (since these layouts are all ViewGroups). When you have a specific layout, for example a LinearLayout, you can use LinearLayout.LayoutParams to get access to things specific to that type of layout. In this case, the layout_weight is particular to LinearLayouts, therefore you must be using the LinearLayout.LayoutParams to access this weight parameter.
You should also remove the layout_weight="0" from the TextView in your XML. If this still doesn't fix it, give your LinearLayout a background color and see if it's even visible at all, then edit your OP with your findings and any changed/new code.