Spannable String only working for last Item in ListView

旧时模样 提交于 2019-11-26 18:37:55

问题


Edit:

I'm now able to strike words in the listview but only able to do it from the bottom up and can only unstrike the bottom item afterwards. The code is below.

@Override
public View getView(int position, View convertView, ViewGroup parent) {
    final SubTask subTask = getItem(position);

    if(convertView == null){
        convertView = LayoutInflater.from(getContext()).inflate(R.layout.subtask_adapter_list, parent, false);
    }

    final TextView tasksTitle = (TextView)convertView.findViewById(R.id.tasksName);
    tasksTitle.setTypeface(typeface);
    completed = (CheckBox)convertView.findViewById(R.id.completed);
    changeColor();

    tasksTitle.setText(subTask.getSubTask());

    completed.setChecked(subTask.isCompleted());

    toggleLineThrough(tasksTitle, tasksTitle.getText().toString(), subTask.getSubTask(), completed.isChecked());

    completed.setOnClickListener(new View.OnClickListener() {
        @Override
        public void onClick(View view) {
            if(completed.isChecked()){
                subTask.setCompleted(true);
                subTask.save();
                toggleLineThrough(tasksTitle, tasksTitle.getText().toString(), subTask.getSubTask(), completed.isChecked());
            } else {
                subTask.setCompleted(false);
                subTask.save();
                toggleLineThrough(tasksTitle, tasksTitle.getText().toString(), subTask.getSubTask(), completed.isChecked());
            }
        }
    });

    return convertView;
}

private void toggleLineThrough(TextView tasksTitle, String title, String oldTitle, boolean completed){
    SpannableString styledString = new SpannableString(title);
    if(completed == true){
        styledString.setSpan(new StrikethroughSpan(), 0, title.length(), 0);
        tasksTitle.setText(styledString);
    } else {
        tasksTitle.setText(oldTitle);
    }
}

I'm trying to have the strike going across a TextView if it's criteria is met. Logically it works, but only for the last Item in the ListView. All other Items don't seem to create the strike at all. My code is below:

@Override
public View getView(int position, View convertView, ViewGroup parent) {
    subTask = getItem(position);

    if(convertView == null){
        convertView = LayoutInflater.from(getContext()).inflate(R.layout.subtask_adapter_list, parent, false);
    }

    tasksTitle = (TextView)convertView.findViewById(R.id.tasksName);
    tasksTitle.setTypeface(typeface);
    completed = (CheckBox)convertView.findViewById(R.id.completed);
    changeColor();

    tasksTitle.setText(subTask.getSubTask());

    toggleLineThrough(subTask.getSubTask());

    completed.setChecked(subTask.isCompleted());

    completed.setOnClickListener(new View.OnClickListener() {
        @Override
        public void onClick(View view) {
            if(completed.isChecked()){
                subTask.setCompleted(true);
                subTask.save();
                toggleLineThrough(subTask.getSubTask());
            } else {
                subTask.setCompleted(false);
                subTask.save();
                toggleLineThrough(subTask.getSubTask());
            }
        }
    });

    return convertView;
}

private void toggleLineThrough(String text){
    SpannableString styledString = new SpannableString(text);
    if(completed.isChecked()){
        styledString.setSpan(new StrikethroughSpan(), 0, text.length(), 0);
        tasksTitle.setText(styledString);
    } else {
        tasksTitle.setText(text);
    }
}

I just can't seem to get the strike to work for them all. I've tried also using the Paint Flags way with the same result (even hiding my TextView altogether). Any help using Spannable String for my TextViews in the ListView would be really appreciated.


回答1:


You should pass checked state and tasksTitle to toggleLineThrough() too. Variables completed and tasksTitle contain wrong values because onClickListener is called later than getView(). They were overwritten several times by consecutive calls of getView().




回答2:


@SmiffyKmc, I am trying to use the following to answer other similar questions, so some comments may not related to your problem.

Have made a completed sample here:

in strings.xml:

    <string-array name="list_items_sample">
    <item>One</item>
    <item>Two</item>
    <item>Three</item>
    <item>Four</item>
    <item>Five</item>
    <item>Six</item>
    <item>Seven</item>
    <item>Eight</item>
    <item>Nine</item>
    <item>Ten</item>
    <item>Eleven</item>
    <item>Twelve</item>
    <item>Thirteen</item>
    <item>Fourteen</item>
    <item>Fifteen</item>
    <item>Sixteen</item>
    <item>Seventeen</item>
    <item>Eighteen</item>
    <item>Nineteen</item>
    <item>Twenty</item>
</string-array>

activity_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="${relativePackage}.${activityClass}" >

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

</ListView>

custom_list_items.xml:

<?xml version="1.0" encoding="utf-8"?>
<RelativeLayout xmlns:android="http://schemas.android.com/apk/res/android"
android:id="@+id/RelativeLayout1"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:orientation="horizontal"
android:paddingBottom="20dp"
android:paddingTop="20dp" >

<CheckBox
    android:id="@+id/cb"
    android:layout_width="wrap_content"
    android:layout_height="wrap_content"
    android:layout_alignParentEnd="true"
    android:layout_alignParentRight="true"
    android:focusable="false" />

<TextView
    android:id="@+id/tvId"
    android:layout_width="wrap_content"
    android:layout_height="wrap_content"
    android:layout_alignBaseline="@+id/cb"
    android:layout_alignParentLeft="true"
    android:layout_alignParentStart="true"
    android:layout_alignParentTop="true"
    android:layout_marginBottom="5dp"
    android:layout_marginTop="5dp"
    android:gravity="center"
    android:minWidth="70dp"
    android:textSize="25sp" />

<TextView
    android:id="@+id/tvDescription"
    android:layout_width="match_parent"
    android:layout_height="wrap_content"
    android:layout_alignBaseline="@+id/cb"
    android:layout_alignParentTop="true"
    android:layout_toEndOf="@id/tvId"
    android:layout_toLeftOf="@+id/cb"
    android:layout_toRightOf="@id/tvId"
    android:layout_toStartOf="@id/cb" />

MainActivity.java:

public class MainActivity extends Activity {
static final String ITEM_ID = "item_id";
static final String ITEM_NAME = "item_name";
static final String ITEM_CHECKED ="item_checked";
ListView mListView;

ArrayList<HashMap<String, Object>> mDataList = new ArrayList<>();
ArrayAdapter<HashMap<String, Object>> mArrayAdapter;

@Override
protected void onCreate(Bundle savedInstanceState) {
    super.onCreate(savedInstanceState);
    setContentView(R.layout.activity_main);

    mListView = (ListView) findViewById(R.id.listView1);

    String items[] = getResources().getStringArray(R.array.list_items_sample);
    mDataList.clear();
    for(int i=1; i<20; i++){
        HashMap<String, Object> newItem = new HashMap<String, Object>();
        newItem.put(ITEM_ID, String.valueOf(i));
        newItem.put(ITEM_NAME, "Sample<"+items[i-1]+">");
        newItem.put(ITEM_CHECKED, false);
        mDataList.add(newItem);
    }

    mListView.setChoiceMode(ListView.CHOICE_MODE_SINGLE);

    mArrayAdapter = new ArrayAdapter<HashMap<String, Object>>(getApplicationContext(), R.layout.custom_list_items, mDataList){
        class ViewHolder{
            CheckBox cb;
            TextView tvId;
            TextView tvDescription;
        }
        @Override
        public View getView(int position, View convertView, ViewGroup parent) {
            ViewHolder holder;
            // Prepare views ready for data
            if(convertView == null){
                convertView = LayoutInflater.from(getContext()).inflate(R.layout.custom_list_items, parent, false);
                holder = new ViewHolder();
                holder.cb = (CheckBox) convertView.findViewById(R.id.cb);
                holder.tvId = (TextView) convertView.findViewById(R.id.tvId);
                holder.tvDescription = (TextView) convertView.findViewById(R.id.tvDescription);
                convertView.setTag(holder);

                holder.cb.setOnClickListener(new OnClickListener(){
                    @Override
                    public void onClick(View view) {
                        // Get view, position and DataList(position)
                        CheckBox buttonView = (CheckBox) view;
                        int pos = (int) buttonView.getTag();
                        HashMap<String, Object> selectedItem = new HashMap<String, Object>();
                        selectedItem = getItem(pos);

                        // Update DataList
                        remove(selectedItem);
                        selectedItem.remove(ITEM_CHECKED);
                        if(buttonView.isChecked()){
                            selectedItem.put(ITEM_CHECKED, true);
                            insert(selectedItem, pos);
                        }else{
                            selectedItem.put(ITEM_CHECKED, false);
                            insert(selectedItem, pos);
                        }

                        // Update all UI views by
                        notifyDataSetChanged();
                        // or Update the affected views directly
//                          View itemView = (View) buttonView.getParent();
//                          TextView tv = (TextView) itemView.findViewById(R.id.tvDescription);
//                          toggleLineThrough(tv, (String)getItem(pos).get(ITEM_NAME), buttonView.isChecked());
                    }
                });
            }else{
                holder = (ViewHolder) convertView.getTag();
            }

            // Get data from DataList
            HashMap<String, Object> currentItem = getItem(position);

            // Setup List items UI
            holder.tvId.setText((String)currentItem.get(ITEM_ID));
            holder.cb.setChecked((boolean)currentItem.get(ITEM_CHECKED));
            toggleLineThrough(holder.tvDescription, (String)currentItem.get(ITEM_NAME), (boolean)currentItem.get(ITEM_CHECKED));

            // Save position to CheckBox, so position can be retrieved when CheckBox is clicked
            holder.cb.setTag(position);

            return convertView;
        }
        private void toggleLineThrough(TextView tasksTitle, String title, boolean completed){
            SpannableString styledString = new SpannableString(title);
            if(completed == true){
                styledString.setSpan(new StrikethroughSpan(), 0, title.length(), 0);
                tasksTitle.setText(styledString);
            }else{
                tasksTitle.setText(title);
            }
        }
    };
    mListView.setAdapter(mArrayAdapter);
}
}

Some important points:

  1. Keep state of the CheckBox as a field in data list and setup UI accordingly, otherwise ListView will be wrong after scroll.
  2. getView() is over when the ListView is shown. OnClickListener SHOULD NOT use the data obtained from getView().
  3. OnClickListener SHOULD ABLE and NEED to get data from data list. The simplest method may be: "View.setTag(position) in getView(), View.getTag() in OnClickListener()".
  4. Don't just modified views only. Change data, data list first. Then call notifyDataSetChanged(). If there is only a small change to UI, can do it directly but should be the same as calling notifyDataSetChanged().

Hope that help!



来源:https://stackoverflow.com/questions/39926388/spannable-string-only-working-for-last-item-in-listview

易学教程内所有资源均来自网络或用户发布的内容,如有违反法律规定的内容欢迎反馈
该文章没有解决你所遇到的问题?点击提问,说说你的问题,让更多的人一起探讨吧!