Multi-pane layout - android

I'd like to create an application for tablets using 2 panels. The left one as a menu and the right one as a content presenter. In fact the behavior I expect is similar to the one in Gmail app.
I'm currently using 2 fragments. One is basically a ListView with room names plus some buttons. The other one (the content) includes some information about the selected room.
The room I select in the left panel should stay highlighted - like in the Gmail app. I was trying for many hours to achieve this effect using selectors, but failed.
Moreover I found 2 different opinions about this approach..
1) "Do not try to keep the focus or selection in touch mode"
2) "In general, use the pane on the right to present more information about the item you selected in the left pane. Make sure to keep the item in the left pane selected in order to establish the relationship between the panels."
Can you tell me how to achieve this (just to keep the selected item distinguished from the rest)?
Maybe a ListView isn't the best approach here?
UPDATE
Thanks to Christoph Eberhardt's answer I created a sample project that works fine. It's available on github.

Create an xml file in res/drawable/list_item_selector.xml
<?xml version="1.0" encoding="utf-8"?>
<selector xmlns:android="http://schemas.android.com/apk/res/android">
<item android:state_selected="true" android:drawable="#drawable/list_pressed" />
<item android:drawable="#drawable/list_default" />
</selector>
For the list elements make an own xml file res/layout/list_item.xml
<?xml version="1.0" encoding="utf-8"?>
<TextView xmlns:android="http://schemas.android.com/apk/res/android"
android:id="#android:id/text1"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:textAppearance="?android:attr/textAppearanceLarge"
android:gravity="center_vertical"
android:minHeight="?android:attr/listPreferredItemHeight"
android:paddingLeft="20dp"
android:background="#drawable/list_item_selector"
/>
Then when creating the list adapter do:
setListAdapter(new CustomAdapter<String>(getActivity(),
R.layout.list_item, stringArray));
Now all you have to do is providing the drawables list_default and list_pressed and list_altered
The CustomAdpater looks like:
package com.test.listview_keep_selected;
import android.app.Activity;
import android.content.Context;
import android.graphics.drawable.StateListDrawable;
import android.view.View;
import android.view.View.OnClickListener;
import android.view.ViewGroup;
import android.widget.ArrayAdapter;
import android.widget.ListView;
public class CustomAdapter<T> extends ArrayAdapter<T> {
private Context m_cContext;
public CustomAdapter(Context context, int textViewResourceId,
T[] objects) {
super(context, textViewResourceId, objects);
this.m_cContext = context;
}
#Override
public View getView(int position, View convertView, ViewGroup parent) {
final View returnView = super.getView(position, convertView, parent);
final ListView listView = (ListView) ((Activity) m_cContext).findViewById(R.id.listViewTest);
returnView.setOnClickListener(new OnClickListener(
) {
#Override
public void onClick(View v) {
for(int i = 0; i< listView.getChildCount(); i++)
listView.getChildAt(i).setSelected(false);
StateListDrawable states = new StateListDrawable();
states.addState(new int[] {android.R.attr.state_selected},
m_cContext.getResources().getDrawable(R.drawable.list_pressed));
states.addState(new int[] { },
m_cContext.getResources().getDrawable(R.drawable.list_altered));
v.setBackgroundDrawable(states);
v.setSelected(true);
}
});
return returnView;
}
}
Edit:
And if you don't have it already:
listView.setChoiceMode(ListView.CHOICE_MODE_SINGLE);
Edit:
Don't set the choice mode with a plain ListView, only makes sense if you have e.g. a ListFragment as I have in my code
It's not perfect now, but from now on you should be fine after playing around a bit.
What also might help is: ListView item background via custom selector
Edit:
Now it should work as you want it^^

Related

Set contentDescription dynamically when populating list of TextViews

I'm trying to update Android's sample code "API Demos" application so that TextView items in the list of the default activity have the "content description" needed for accessibility. This is what the initialization looks like for the list in this activity:
public void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
Intent intent = getIntent();
String path = intent.getStringExtra("com.example.android.apis.Path");
if (path == null) {
path = "";
}
setListAdapter(new SimpleAdapter(this, getData(path),
android.R.layout.simple_list_item_1, new String[] { "title" },
new int[] { android.R.id.text1 }));
getListView().setTextFilterEnabled(true);
}
And the getData function creates a Map with the appropriate intentions specified for when someone taps on one of the TextView list elements. Internally, it uses this function:
protected void addItem(List<Map<String, Object>> data, String name, Intent intent) {
Map<String, Object> temp = new HashMap<String, Object>();
temp.put("title", name);
temp.put("intent", intent);
data.add(temp);
}
This is how the Map is populated with title, which as you can see above is mapped to #android/text1 in the layout XML.
I want to set the content description of each of the TextViews with the same value as name. Things I've tried:
(1): Looping through child items of the ListView that is generated and calling item.setContentDescription(xxx) on each of them. This didn't work; apparently at the point I'm looping through the items they're not visible/accessible/existent and either way trying to call setContentDescription blows up.
(2): Creating a new layout XML for this modified TextView, that includes android:contentDescription. The layout XML looked like this
<?xml version="1.0" encoding="utf-8"?>
<TextView xmlns:android="http://schemas.android.com/apk/res/android"
android:id="#android:id/text1"
android:contentDescription="#android:id/text2"
android:layout_width="fill_parent"
android:layout_height="wrap_content"
android:textAppearance="?android:attr/textAppearanceLarge"
android:gravity="center_vertical"
android:paddingLeft="6dip"
android:minHeight="?android:attr/listPreferredItemHeight">
</TextView>
I then made sure that "contentDescription" was added to the Map returned by addItem, and changed the initialization code to look like this:
setListAdapter(new SimpleAdapter(this, getData(path),
R.layout.simple_list_item_with_desc, new String[] { "title", "contentDescription" },
new int[] { android.R.id.text1, android.R.id.text2 }));
In other words, I was trying to get "contentDescription" from my static data map to populate the field in the layout XML defined by android.R.id.text2.
This also didn't work--no failure, but when I examine the list in uiautomatorviewer, no content description is available. It could be because I don't understand what android.R.id.text2 means, other than being a placeholder for a value of some kind. I also tried android.R.id.content with the same result.
So my question is, how do I set the contentDescription of these TextViews dynamically?
I want to set the content description of each of the TextViews with the same value as name. Things I've tried:
(1): Looping through child items of the ListView that is generated and calling item.setContentDescription(xxx) on each of them. This didn't work; apparently at the point I'm looping through the items they're not visible/accessible/existent and either way trying to call setContentDescription blows up.
You are correct, your first method failed because none of the TextViews have been created yet. The problem is they are never visible all at the same time (unless you only have a handful of rows and they fit on the screen without scrolling.)
Solution: Extend SimpleAdapter and override getView(). This method is called each time a row is displayed. It gives you access to the TextView so you can call setContentDescription() here.
#Sam provided a solution, and I wanted to also contribute back the code to my extension class in case it's useful to anyone else:
package com.example.android.apis;
import java.util.List;
import java.util.Map;
import android.content.Context;
import android.view.View;
import android.view.ViewGroup;
import android.widget.SimpleAdapter;
import android.widget.TextView;
public class SimpleAdapterWithDesc extends SimpleAdapter {
public SimpleAdapterWithDesc(Context context,
List<? extends Map<String, ?>> data, int resource, String[] from,
int[] to) {
super(context, data, resource, from, to);
}
public View getView(int position, View convertView, ViewGroup parent) {
TextView myView = (TextView)super.getView(position, convertView, parent);
myView.setContentDescription(myView.getText());
return myView;
}
}

Android - Show textview by clicking on listview item

I am creating my very first Android application, but i stuck unfortunately. The application would be very simple: On the starting page there is a ListView with items like:
1st group
2nd group
3rd group
...
By clicking on any of these items a new page would show up with a single textview element that would have some description. Like you click on '1st group' item, the listview gets hidden, and a new page appears with '1st group description' text.
So far I can show the listview with the items, but when I click on them, nothing happens (i guess I miss some basic stuff, but as a very newby, i cannot find it out easily).
import java.util.ArrayList;
import java.util.Arrays;
import android.app.Activity;
import android.os.Bundle;
import android.widget.AdapterView;
import android.widget.AdapterView.OnItemClickListener;
import android.widget.ArrayAdapter;
import android.widget.LinearLayout;
import android.widget.LinearLayout.LayoutParams;
import android.widget.ListView;
import android.widget.TextView;
import android.view.*;
public class SimpleListViewActivity extends Activity {
LinearLayout.LayoutParams layoutParams;
LinearLayout ll;
private ListView mainListView ;
private ArrayAdapter<String> listAdapter ;
#Override
public void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.main);
// Find the ListView resource.
mainListView = (ListView) findViewById( R.id.mainListView );
// populate the List of groups
String[] GROUP = getResources().getStringArray(R.array.group);
ArrayList<String> GrList = new ArrayList<String>();
GrList.addAll( Arrays.asList(GROUP) );
// Create ArrayAdapter using the list.
listAdapter = new ArrayAdapter<String>(this, R.layout.simplerow, GrList);
// Set the ArrayAdapter as the ListView's adapter.
mainListView.setAdapter( listAdapter );
ll = (LinearLayout)findViewById(R.id.LinearLayout);
layoutParams = new LinearLayout.LayoutParams
(LayoutParams.WRAP_CONTENT, LayoutParams.WRAP_CONTENT);
mainListView.setOnItemClickListener(new OnItemClickListener() {
public void onItemClick(AdapterView<?> parent, View view, int position, long id) {
TextView t = (TextView) findViewById(R.id.textView1);
String[] DESC = getResources().getStringArray(R.array.desc);
t.setText(DESC[position]);
ll.addView(t);
//This is the point that is wrong for sure (and others maybe also). I cannot get the textview shown
}
});
}
}
Thanks for your help.
Have you tried displaying a toast message or setting a breakpoint within your onItemClick() method to verify that its not being reached? My guess is that it is and you are running into one of the issues described here:
Refreshing a LinearLayout after adding a view
I am assuming your R.layout.main is holding a listview and a linear layout with ids R.id.mainListView, and R.id.LinearLayout respectively.
Example: I left out some of the obvious attributes you would need like height width etc..
<RelativeLayout xmlns:android="http://schemas.android.com/apk/res/android"
>
<ListView android:id="#+id/mainListView" />
<LinearLayout android:id="#+id/LinearLayout" />
</RelativeLayout>
In your on item click, all you will do is add a textview as you have done, then set the mainListView.setVisibility(View.INVISIBLE) and ll.setVisibility(View.VISIBLE).
If your R.layout.main is not using a RelativeLayout as the root node, but is instead using a LinearLayout you should still be able to achieve the same effect by setting the Visibilities to View.GONE if you want it to hide, and View.VISIBLE if you want it to show.
To revert back to being able to see the list view I would override onBackPressed() in the activity, to invert the Visibilities on the two items. Also remember to remove all views from the linear layout so that the next time an item in the group is selected it will be the only item in the linear layout when it is added.
There are much easier ways to accomplish this, such as firing off a new activity for viewing the next item, but seems you are keeping everything within one activity. I would also think about using a ListActivity instead of base activity class.
Hope this helps.
First off stop using the word page. Call it an activity (gotta get you in the Android zone)
Once the click happens start a new activity like so:
mainListView.setOnItemClickListener(new OnItemClickListener() {
String textToPass = GrList.get(position)
Intent i = new Intent(getBaseContext(), SecondActivity.class);
i.putExtra("textToPass", textToPass);
startActivity(i);
}
You'll obviously need to have that second activity with its corresponding layout file defined. Also in the second activity look up how to get the bundle and extras from the first activity in order to get the textToPass String

How To End Drop Down Mode for an Android Custom Spinner

I am creating a Spinner instance that I bind to my own custom adapter class (derived from BaseAdapter). In it, I create a bunch of custom "cell" LinearLayout-derived instances that I inflate from xml to be used for each item in the spinner.
Everything looks fine as expected, but whenever the items in "drop down" mode are clicked, the drop down dialog does not get dismissed.
I can trap the click notification inside my cell class, but what is the magic sauce I need to tell the spinner that an item was selected and that it should dismiss the drop down?
My cell class is inflated from the following xml:
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
android:id="#+id/VIEW_LEAF"
android:layout_width="fill_parent"
android:layout_height="wrap_content"
android:orientation="horizontal"
>
<TextView android:id="#+id/STATIC_LEAF_NAME"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:gravity="left"
android:paddingLeft="3dip"
android:paddingTop="20dip"
android:paddingBottom="20dip"
android:textSize="22sp"
android:textStyle="bold"
android:text=""
android:layout_gravity="left|center_vertical"
android:textColor="#color/black_enabled_grey_disabled"
/>
</LinearLayout>
My cell class looks like so:
import android.content.Context;
import android.util.Log;
import android.view.LayoutInflater;
import android.view.View;
import android.view.ViewGroup;
import android.view.ViewParent;
import android.widget.LinearLayout;
import android.widget.Spinner;
import android.widget.TextView;
import android.widget.Toast;
import android.view.View.OnClickListener;
// This class is responsible for rendering the data in the model.
public class DataCell extends LinearLayout implements OnClickListener
{
private final String TAG = getClass().getSimpleName();
public DataCell(Context context)
{
super(context);
m_context = context;
final boolean attachToRoot = true;
LayoutInflater.from(context).inflate(R.layout.data_cell,
this, attachToRoot);
m_labelName = (TextView) findViewById(R.id.STATIC_LEAF_NAME);
m_labelName.setOnClickListener(this);
}
// Update the views with the data corresponding to selection index.
// If dropDown is true, this view is to be displayed in a dropped down
// list.
public void display(int index, ViewGroup parent, Data data, boolean dropDown)
{
m_data = data;
m_labelName.setText(data.getFriendlyName());
setFocusable(true);
}
#Override
public void onClick(View view)
{
ViewParent parent = getParent();
if(parent instanceof Spinner)
{
//((Spinner)parent).setSelection(2);// TODO something here?
}
}
private TextView m_labelName = null;
#SuppressWarnings("unused")
private Context m_context = null;
#SuppressWarnings("unused")
private Data m_data = null;
}
Thanks!
swine
So you were right about the cell stealing the clicks. But there was a very simple solution to all this, just not very obvious. All I had to do is make the TextView inside my DataCell not respond to clicks.
So I removed the m_labelName.onClickListener() call completely, and instead, at that location, put in the following code:
m_labelName.setClickable(false);
m_labelName.setFocusable(false);
m_labelName.setFocusableInTouchMode(false);
I would delete the entire DataCell class, since it is doing you no good. If I had to guess, 99.9% of the uses of Spinner do not involve creating your own subclass of LinearLayout.
If there is more to this class than you elected to show, enough to make it worthwhile, then you should not be intercepting and consuming the click events. Spinner handles that automatically. If I had to guess, 99.999% of the uses of Spinner do not involve the contents of the dropdown list being involved in click events.

ListView item won't stay "selected"

I want to change the background of a listview item when the user clicks it. Kind of like the Honeycomb settings page (Although I'm not dealing with just settings so I'm not using PreferenceActivity) I have this functionality working through a resource state selector state selector except for the cases when clicking on the listview menu changes the linear layout to the right of the listview (sort of a split screen view). I'm guessing the listview looses focus so state_pressed is no longer true.
<item android:state_pressed="true">
<shape >
<solid android:color="#color/blue1" />
</shape>
</item>
Any tips to keep that listview item colored until another listview item is selected? Thanks!
EDIT:
I was able to get the background changed in a setOnItemClickListener with
view.setBackgroundResource(R.color.red);
I only need one selected at a time so when the other list items are clicked, I tried lv.invalidate() and lv.getChildAt(0).invalidate() but neither worked and the second causes null pointer exception. Any ideas for putting the color back?
When you release your finger from the cell it no longer registers as pressed. What you are going to want to do is actually change the background of the individual row when a users selects is. This means implementing an onItemClick or onItemTouch and flagging the adapter to redraw the row with the new background. If you are already using a custom list adapter you can just implement a check against a boolean in your getView() method. You will also need to keep track which rows are 'selected' and which are not.
pseudocode:
public View getView(int pos, View convertView, ViewGroup parent) {
if(isChecked[pos]) //set background to checked color
}
Hope this help,
1.- Create a shape file for focused item: \drawable\list_selector_focused.xml
<shape xmlns:android="http://schemas.android.com/apk/res/android" android:shape="rectangle">
<gradient android:angle="90" android:startColor="#f5c98c" android:endColor="#f7ddb8"/>
</shape>
2.- Create a shape file for pressed item: \drawable\list_selector_pressed.xml
<shape xmlns:android="http://schemas.android.com/apk/res/android" android:shape="rectangle">
<gradient android:angle="90" android:startColor="#fb9d23" android:endColor="#ffc579" />
</shape>
3.- Create list selector file: \drawable\list_selector.xml
<selector xmlns:android="http://schemas.android.com/apk/res/android">
<item android:state_pressed="true" android:drawable="#drawable/list_selector_pressed" />
<item android:state_focused="true" android:drawable="#drawable/list_selector_focused" />
<item android:drawable="#drawable/list_selector_focused" />
</selector>
4.- Add this attribute to your ListView in the layout file:
android:choiceMode="singleChoice"
android:listSelector="#drawable/list_selector"
You can use colors instead of gradient shapes,
This is implementation of sgarman idea:
package com.mypackage;
import java.util.Vector;
import com.myapp.R;
import com.myapp.data.Address;
import android.content.Context;
import android.graphics.Color;
import android.util.Log;
import android.view.LayoutInflater;
import android.view.View;
import android.view.ViewGroup;
import android.widget.BaseAdapter;
import android.widget.TextView;
public class AddressesListAdapter extends BaseAdapter{
protected Context context;
protected LayoutInflater mInflater;
protected int itemResourceId;
protected Vector<Address> contentItems = new Vector<Address>();
protected Vector<Boolean> selectedStates;
private static final String TAG = "myapp";
public AddressesListAdapter(Context context, Vector<Address> contentItems) {
this.context = context;
this.contentItems = contentItems;
mInflater = LayoutInflater.from(context);
itemResourceId = R.layout.address_list_item;
selectedStates = new Vector<Boolean>();
//initial fill
clearSelectedState();
}
#Override
public int getCount() {
return contentItems.size();
}
#Override
public Object getItem(int position) {
return contentItems.get(position);
}
#Override
public long getItemId(int position) {
return position;
}
#Override
public View getView(final int position, View convertView, ViewGroup parent) {
final ViewHolder holder;
if (convertView == null) {
convertView = mInflater.inflate(itemResourceId, null);
holder = new ViewHolder();
holder.addressName = (TextView) convertView.findViewById(R.id.addressName);
convertView.setTag(holder);
} else {
holder = (ViewHolder) convertView.getTag();
}
Address address = (Address) contentItems.get(position);
holder.addressName.setText(address.getAddressName());
holder.addressName.setOnClickListener(new SetFocusListener(position));
//restore saved position from saving vector
if (selectedStates.get(position)) holder.addressName.setBackgroundColor(Color.BLUE);
else holder.addressName.setBackgroundColor(Color.TRANSPARENT);
return convertView;
}
private void clearSelectedState () {
selectedStates.clear();
for (int i = 0 ; i <= contentItems.size(); i++) {
selectedStates.add(new Boolean(false));
}
}
private class SetFocusListener implements View.OnClickListener {
private int position;
public SetFocusListener(int position) {
this.position = position;
}
#Override
public void onClick(View v) {
//clear selected state vector
clearSelectedState();
//set selected position
selectedStates.set(position, new Boolean(true));
//refresh adapter to redraw focus
notifyDataSetChanged();
}
}
static class ViewHolder {
TextView addressName;
}
}
Th only concern that it may be to costly to setup new listener for every getView() iteration
The android state checked is best used to resolve this issue.
Someone mentioned using android:background="?android:attr/activatedBackgroundIndicator".
This just points to one of the activated_background_* resources in frameworks/base/core/res/res/drawable of the android source code. For example activated_background_holo_dark.xml:
<selector xmlns:android="http://schemas.android.com/apk/res/android">
<item android:state_activated="true" android:drawable="#android:drawable/list_activated_holo" />
<item android:drawable="#color/transparent" />
</selector>
So essentially you want to use state_activated to represent when the user presses the button as well when it is in a checked (i.e. in a persistent selected state) state. Note that activated was only introduced after Honeycomb, if you are targeting older devices you'll need to rely on state_checked (more details here).
Now if you want to set an item as checked, you need to call listView.setItemChecked(position, true). You'll likely want to set the android:choiceMode property on your ListView to the appropriate value (e.g. if you want only one thing selected at a time use singleChoice). You don't need to invalidate, the call to setItemChecked will trigger a relayout which will update the view.
Also be careful if you allow reordering items in your ListView as the current checked item(s) will need to be updated. If you use stable Ids, this will be handled automatically.
To see an example of this in action, check out the NavigationDrawer sample code found in the training series: http://developer.android.com/training/implementing-navigation/nav-drawer.html.
By default, 'Selected' isn't the same as 'Clicked' when you're using a touch interface - something that cause me some real headaches when I started Android development.
To support both users that navigate by touch and users that use scrollwheels/trackballs, you might want to use setSelection, and do your manipulation in an AdapterView.OnItemSelectedListener implementation (set with setOnItemSelectedListener).
Another gotcha is that setSelection won't highlight an item if the last event was a touch event.
I'd recommend that you create a custom View for your list items, and handle highlighting in there.
Hope this helps,
Phil Lello
Ok, so I tried the solution above from where it says "This is implementation of sgarman idea:" This only works if SetFocusListener is an OnTouchListner. Ohterwise the onClick method consumes the click. I had to pair this solution with an OnItemClick listener on my list item to get the list to actually show the highlighted item.
I used android:state_activated="true" instead of state_selected. It works like a charm!
If you keep your listView through the whole activity you can do a mListView.isItemChecked(position) in the getView() method. And them set the background color depending on the result.
Have you tried android:state_selected="true" ?
try
android:background="?android:attr/activatedBackgroundIndicator"
;)

Making a list view like the favorite tab on native dialer in android

I am making an android application that shows a list with users, I would like to add the image,name,status,and a button in every row of the list, just like in the androids 1.6 native caller-> favorites tab.Until know I have managed to add the image and name using an ListActivity but when I try to add the button the list becomes unselected.So I have 2 questions, first is the list mentioned above listviews or listactivities? also is this possible using listActivity? and second, what is the difference of the above mentioned classes? any link to tutorials would be appreciated.
You will need to extend ListActivity and ListAdapter to implement your design. The activity that displays your list should extend ListActivity instead of Activity. In the onCreate method of your ListActiviry, the content view of your activity should be set to a linear layout that is the parent of a listview. The list view must have id "#+id/android:list". You can also include a textview to be displayed when the list is empty, see below. Also in OnCreate call setListAdapter() and pass in a new object of your that extends ListAdapter.
In the class that you make that extends ListAdapter, override all of the methods that you need to, especially getView().
Example code:
MyListActivity.java
import android.app.ListActivity;
public class MyListActivity extends ListActivity {
public void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.my_list);
setListAdapter(new MyListAdapter());
}
}
my_list.xml
<?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="fill_parent">
<ListView android:id="#+id/android:list"
android:layout_width="fill_parent"
android:layout_height="wrap_content" />
<TextView android:id="#+id/android:empty"
      android:layout_width="fill_parent"
      android:layout_height="wrap_content"
     android:text="No Events!"/>
</LinearLayout>
MyListAdapter.java
import android.widget.ListAdapter;
public class MyListAdapter implements ListAdapter {
//Methods to load your data
public View getView(int arg0, View reuse, ViewGroup parent) {
//Create the view or if reuse is not null then reuse it.
//Add whatever kind of widgets you want here and return the view object
}
}

Categories

Resources