0

I have written a custom adapter to load the list for my ListView. It works. Currently, I am interested in making it respond to click so that an email could be sent. I added the code to send email (like this) to my click listener.

This is from my custom adapter; the context passed to the constructor MessagesAdapter() is getApplicationContext(). (I did try with this as suggested here; but, it didn't work.)

public MessagesAdapter(Context context, int textViewResourceId, ArrayList<String> fileRecords) {
    super(context, textViewResourceId, fileRecords);
    this.ctx = context;
    this.l = fileRecords;
}

@SuppressLint("ViewHolder")
@Override
public View getView(int position, View convertView, ViewGroup parent) {
LayoutInflater inflater = (LayoutInflater) ctx
    .getSystemService(Context.LAYOUT_INFLATER_SERVICE);
View rowView = inflater.inflate(R.layout.activity_messages_item, parent, false);

TextView hView = (TextView) rowView.findViewById(R.id.messageHeader);
TextView dView = (TextView) rowView.findViewById(R.id.messageDetail);
TextView fView = (TextView) rowView.findViewById(R.id.messageFooter);

String s = l.get(position);
String[] parts = s.split(";");

hView.setText(parts[0]);
rm.setmHeader(parts[0]);
hView.setTextSize(TypedValue.COMPLEX_UNIT_SP, ctx.getResources().getDimension(R.dimen.header_text));

dView.setText(parts[1]);
rm.setmDetail(parts[1]);
hView.setTextSize(TypedValue.COMPLEX_UNIT_SP, ctx.getResources().getDimension(R.dimen.detail_text));

fView.setText(parts[2]);
rm.setmFooter(parts[2]);
hView.setTextSize(TypedValue.COMPLEX_UNIT_SP, ctx.getResources().getDimension(R.dimen.footer_text));

dView.setOnClickListener(new View.OnClickListener() {

        @Override
        public void onClick(View v) {
            Intent intent = new Intent(Intent.ACTION_SEND);
            intent.setType("text/plain"); 
            intent.setFlags(Intent.FLAG_ACTIVITY_NEW_TASK);
            intent.putExtra(Intent.EXTRA_EMAIL, rm.getmDetail().split(" ")[1]);
            intent.putExtra(Intent.EXTRA_SUBJECT, ctx.getResources().getString(R.string.email_subject) + " " + rm.getmHeader());
            ctx.startActivity(Intent.createChooser(intent, ""));
        }
    });

    return rowView;
}

My problem seems to be due to startActivity(). If don't use ctx.startActivity(), I get a null pointer exception. If I do, I get a Android Runtime Exception. This is even with Intent.setFlags(Intent.FLAG_ACTIVITY_NEW_TASK).

07-27 02:50:15.747: E/AndroidRuntime(2092): android.util.AndroidRuntimeException: Calling startActivity() from outside of an Activity  context requires the FLAG_ACTIVITY_NEW_TASK flag. Is this really what you want?

So, here it is.

  1. The Activity sets up the MessageAdapter to the ListView.
  2. The adapter sets up the text views with list data correctly.
  3. The adapter also sets up a View.OnClickListener().

When the displayed message is clicked, the application crashes.

Could you please help me how to implement the click ?

Layout for individual items

<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="match_parent"
tools:context="com.foo.bar.MessageActivity" >

<TextView
    android:id="@+id/messageHeader"
    android:layout_width="wrap_content"
    android:layout_height="wrap_content"
    android:layout_alignParentLeft="true"
    android:layout_alignParentRight="true"
    android:layout_alignParentTop="true"
    android:textStyle="bold"/>

<TextView
    android:id="@+id/messageDetail"
    android:layout_width="match_parent"
    android:layout_height="wrap_content"
    android:layout_alignParentLeft="true"
    android:layout_below="@+id/messageHeader"
    android:autoLink="email"/>

<TextView
    android:id="@+id/messageFooter"
    android:layout_width="match_parent"
    android:layout_height="wrap_content"
    android:layout_alignParentLeft="true"
    android:layout_below="@+id/messageDetail" />

</RelativeLayout>

Layout for list view:

<RelativeLayout xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:tools="http://schemas.android.com/tools"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:paddingBottom="@dimen/activity_vertical_margin"
tools:context="com.foo.bar.MessagesActivity" >
<ListView
    android:id="@android:id/list"
    android:layout_width="wrap_content"
    android:layout_height="wrap_content" 
    android:layout_alignParentLeft="true" >
</ListView>

</RelativeLayout>

Message adapter code

import java.util.ArrayList;


import android.annotation.SuppressLint;
import android.content.Context;
import android.util.TypedValue;
import android.view.LayoutInflater;
import android.view.View;
import android.view.ViewGroup;
import android.widget.ArrayAdapter;
import android.widget.TextView;

public class MessagesAdapter extends ArrayAdapter<String> {
    private Context ctx;
    private ArrayList<String> l;
    ReceivedMessages rm = new ReceivedMessages();

    public MessagesAdapter(Context context, int textViewResourceId, ArrayList<String> fileRecords) {
        super(context, textViewResourceId, fileRecords);
        this.ctx = context;
        this.l = fileRecords;
    }

    @SuppressLint("ViewHolder")
    @Override
      public View getView(int position, View convertView, ViewGroup parent) {
        LayoutInflater inflater = (LayoutInflater) ctx
                .getSystemService(Context.LAYOUT_INFLATER_SERVICE);
        View rowView = inflater.inflate(R.layout.activity_messages_item, parent, false);

        TextView hView = (TextView) rowView.findViewById(R.id.messageHeader);
        TextView dView = (TextView) rowView.findViewById(R.id.messageDetail);
        TextView fView = (TextView) rowView.findViewById(R.id.messageFooter);

        String s = l.get(position);
        String[] parts = s.split(";");

        hView.setText(parts[0]);
        hView.setTextSize(TypedValue.COMPLEX_UNIT_SP, ctx.getResources().getDimension(R.dimen.header_text));
        hView.setClickable(false);
        hView.setLongClickable(false);
        hView.setOnClickListener(null);

        dView.setText(parts[1]);
        dView.setTextSize(TypedValue.COMPLEX_UNIT_SP, ctx.getResources().getDimension(R.dimen.detail_text));
        dView.setClickable(false);
        dView.setLongClickable(false);

        fView.setText(parts[2]);
        fView.setTextSize(TypedValue.COMPLEX_UNIT_SP, ctx.getResources().getDimension(R.dimen.footer_text));
        fView.setClickable(false);
        dView.setOnClickListener(null);

        return rowView;
    }
}

Using the MessageAdapter

// Handler for received Intents
private BroadcastReceiver fileReadReceiver = new BroadcastReceiver() {
    @Override
    public void onReceive(Context context, Intent intent) {
        populateList();
    }
};


private void populateList() {
    String[] values = fc.getMessageArray();
    final ArrayList<String> list = new ArrayList<String>();
    for (int i = 0; i < values.length; i++) {
        list.add(values[values.length-1-i]);   // Reversing the list to show recent messages first
        ++i;
    }

    MessagesAdapter ma = new MessagesAdapter(getApplicationContext(),
            R.layout.activity_messages_item, list);
    final ListView listView = (ListView) findViewById(android.R.id.list);
    listView.setClickable(true);
    listView.setAdapter(ma);

    listView.setOnItemClickListener(new ListView.OnItemClickListener() {

        @Override
        public void onItemClick(AdapterView<?> parent, View view,
                int position, long id) {
            String s = list.get(Long.valueOf(id).intValue());
            Log.d("MessagesActivity", s);

            Intent emailintent = new Intent(android.content.Intent.ACTION_SEND);
            emailintent.setType("plain/text");
            emailintent.putExtra(android.content.Intent.EXTRA_EMAIL,new String[] {"random@gmail.com" });
            emailintent.putExtra(android.content.Intent.EXTRA_SUBJECT, "");
            emailintent.putExtra(android.content.Intent.EXTRA_TEXT,"");
        }

    }) ;
}
Community
  • 1
  • 1
cogitoergosum
  • 2,309
  • 4
  • 38
  • 62
  • 1
    hmm...so, I paid attention to 'similar questions' and noticed this [one](http://stackoverflow.com/q/23252565/919480) is similar to mine. However, in my case, each item of the list is a set of three `TextView`s - header, detail and footer. How do I know which one was clicked ? I did try [this](http://pastebin.com/DmMye1bv) in my `Activity`; but nothing in logcat. – cogitoergosum Jul 27 '14 at 07:19
  • So does the intent chooser appear? – Simas Jul 27 '14 at 07:44
  • @user3249477 No, nothing at all. – cogitoergosum Jul 27 '14 at 07:47
  • I was just thinking - who should be clickable. The `ListView` or the `TextView`s. I did `setClickable(true)` for both; but no result. To log, I also tried `list.get(Long.valueOf(id).intValue())` because I will have access to underlying list and the `id` is from the callback method. Nothing in the log. – cogitoergosum Jul 27 '14 at 07:53
  • Well when do you want the chooser to fire? When a specific list's item is clicked or when a specific textview is clicked? – Simas Jul 27 '14 at 08:00
  • Specific item will do as I would be able to access the corresponding list entry to build the mail properties. – cogitoergosum Jul 27 '14 at 08:52
  • Found an even more similar [question](http://stackoverflow.com/questions/7923730/listview-element-not-clickable) and tried an [answer](http://stackoverflow.com/a/7939056/919480) where the items are set to not clickable. Didn't work for me. However, what seems to be disappointing is this sounds to be a [known bug](http://code.google.com/p/android/issues/detail?id=3414). – cogitoergosum Jul 27 '14 at 09:22
  • Please update your question with the the layouts of independent items and layout containing the listview. – Simas Jul 27 '14 at 09:42
  • Also add the code you create the adapter. It's weird that you can't use this. – Simas Jul 27 '14 at 09:50
  • Umm i meant where you are creating the adapter. Like `new MessagesAdapter()`. That's probably your activity code – Simas Jul 27 '14 at 10:14
  • oh ok. That happens via a `BroadcastReceiver`. (Should have mentioned this earlier ... :D ) Updated now. – cogitoergosum Jul 27 '14 at 10:18
  • Create and set an empty adapter before creating the broadcast receiver. Then onReceive populate the adapter and call notifyDataSetChanged. Set the listener before the receiver also. – Simas Jul 27 '14 at 10:34
  • Let us [continue this discussion in chat](http://chat.stackoverflow.com/rooms/58102/discussion-between-cogitoergosum-and-user3249477). – cogitoergosum Jul 27 '14 at 13:46

2 Answers2

4

You can also create clickable text by using android.text.SpannableString and android.text.style.URLSpan You can also make individual word or part of text clickable in same TextView with different actions. I made this example for you:

MainActivity:

    public class MainActivity extends Activity {

    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_main);
        getFragmentManager().beginTransaction().add(R.id.container, new MainFragment()).commit();

    }

    @Override
    public boolean onCreateOptionsMenu(Menu menu) {

        // Inflate the menu; this adds items to the action bar if it is present.
        getMenuInflater().inflate(R.menu.main, menu);
        return true;
    }
}

MainFragment:

    public class MainFragment extends Fragment{

    @Override
    public View onCreateView(LayoutInflater inflater, ViewGroup container, Bundle savedInstanceState){
        View view = inflater.inflate(R.layout.fragment_main, null);
        ListView list = (ListView)view.findViewById(R.id.listView1);
        ArrayList<String> items = new ArrayList<String>();

        for(int i = 0; i < 10; i++){
            items.add("These both words are clickable: word1 word2");
        }

        ListAdapter adapter = new ListAdapter(getActivity(), items);
        list.setAdapter(adapter);
        return view;
    }

    private class ListAdapter extends BaseAdapter {
        private LayoutInflater mInflater;
        private ArrayList<String> mListItems;

        public ListAdapter(Context context, ArrayList<String> items){
            mInflater = LayoutInflater.from(context);
            mListItems = items;
        }

        @Override
        public int getCount() {
            return mListItems.size();
        }

        @Override
        public Object getItem(int position) {
            return mListItems.get(position);
        }

        @Override
        public long getItemId(int position) {
            return 0;
        }

        public Spannable setSpan(String text){
            Spannable span = new SpannableString(text);
            //Span the word that you want to be clickable, in this case we span two words 
            //These both words are clickable: word1 word2
            //Span word1, this will also underline the word
            span.setSpan(new SpanListener(text.substring(32, 37)), 32, 37, Spannable.SPAN_EXCLUSIVE_EXCLUSIVE);
            span.setSpan(new SpanListener(text.substring(38, 43)), 38, 43, Spannable.SPAN_EXCLUSIVE_EXCLUSIVE);

            return span;
        }

        @Override
        public View getView(int position, View convertView, ViewGroup parent) {
            ViewHolder holder;
            if(convertView == null){
                holder = new ViewHolder();
                convertView = mInflater.inflate(R.layout.list_item, null);

                holder.text = (TextView)convertView.findViewById(R.id.textView1);
                convertView.setTag(holder);

            } else{
                holder = (ViewHolder)convertView.getTag();
            }

            holder.text.setText(setSpan(mListItems.get(position)));
            holder.text.setMovementMethod(LinkMovementMethod.getInstance());

            ((ViewGroup)convertView).setDescendantFocusability(ViewGroup.FOCUS_BLOCK_DESCENDANTS);
            return convertView;
        }



    }

    static class ViewHolder {
        TextView text;
    }

    public class SpanListener extends URLSpan {
        String spanned_text;

        public SpanListener(String text) {
            super(text);
            spanned_text = text;
        }

        public void onClick(View v){
            DialogFragment dialog = new TextDialog();
            Bundle bundle = new Bundle();

            bundle.putString("text", spanned_text);
            dialog.setArguments(bundle);
            dialog.show(getFragmentManager(), "");
        }

        @Override 
        public void updateDrawState(TextPaint paint) {
            super.updateDrawState(paint);
            //Remove underline
            paint.setUnderlineText(false);
        }

    }

    public static class TextDialog extends DialogFragment implements OnClickListener {

        @Override
        public Dialog onCreateDialog(Bundle savedinstanceState){
            final Context context = this.getActivity();
            String text = "";
            Bundle bundle = this.getArguments();
            if(bundle != null){
                text = bundle.getString("text");
            }

            AlertDialog.Builder builder = new AlertDialog.Builder(context);
            builder.setMessage("You clicked: " + text);
            builder.setPositiveButton("OK", this);
            return builder.create();
        }

        @Override
        public void onClick(DialogInterface dialog, int which) {
            // TODO Auto-generated method stub

        }
    }

}

list_item.xml:

<?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="vertical" >

<TextView
    android:id="@+id/textView1"
    android:layout_width="wrap_content"
    android:layout_height="wrap_content"
    android:padding="10dp" />

fragment_main.xml

<RelativeLayout xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:tools="http://schemas.android.com/tools"
android:layout_width="match_parent"
android:layout_height="match_parent"
tools:context="com.example.clickabletextviewinsidelistview.MainActivity$PlaceholderFragment" >

<ListView
    android:id="@+id/listView1"
    android:layout_width="match_parent"
    android:layout_height="wrap_content" >
</ListView>

Result:

user1888162
  • 1,735
  • 21
  • 27
  • Thank you, @user1888162, for a detailed example. I will use it in my next version. Just curious...do you think, your example would work if the list item comprised more than one `TextView` ? – cogitoergosum Jul 28 '14 at 03:05
  • @cogitoergosum Yes, you can put as many textviews as you want in one list item. – user1888162 Jul 28 '14 at 17:15
0

The culprit of this problem is probably android:autoLink="email" together with android:layout_width="match_parent". This fills ListView's item with a big clickable TextView.

Furthermore it seems you were setting it as unclickable, so ultimately digging yourself a hole :-)

If your whole item of the ListView will be clickable, you needn't use the autoLink tag. So just remove that. Everything else seems fine. Hope this works for ya. Happy coding ! :-)

Simas
  • 43,548
  • 10
  • 88
  • 116
  • Yes, the `autoLink` seems to have been a problem. With multiple text views, it works only when the footer text is clicked. Don't know why. For now, I will just merge all and 'make peace'. :D Thank you, @user3249477 for your help ! – cogitoergosum Jul 28 '14 at 03:03