web-dev-qa-db-ja.com

タグのような機能を持つカスタムeditTextの作成

私は探し回っていましたが、これに対する答えは見つかりませんでした。私が実装しようとしているのは、ICS gmailアプリの作成画面にある「To」フィールドに似たEditTextです。

これが私が欲しいものを説明する画像です: enter image description here

私はEditTextを拡張して独自のカスタムEditTextクラスを実装することを考えていますが、それを行う方法や、それが最善の解決策であるかどうかさえわかりません。何かご意見は?

29
bill-x

似たような質問を見つけるのに少し時間がかかりましたが、それでも 私が見つけた最も近い答え です。他の人がこのような問題を抱えていたことは以前から知っていました。私を正しい方向に向けてくれたCommonsWareに感謝します。

7
bill-x

この答え からの解決策を適応させました。カンマを挿入するときに、入力を自動的に区切ります(セパレーターは調整できます)。 ImageSpanとClickableSpanを作成します(右側の部分をクリックすると、エントリを削除できます)。

public class TagEditText extends EditText {

    TextWatcher textWatcher;

    String lastString;

    String separator = ",";

    public TagEditText(Context context, AttributeSet attrs) {
        super(context, attrs);
        init();
    }


    private void init() {
        setMovementMethod(LinkMovementMethod.getInstance());

        textWatcher = new TextWatcher() {
            @Override
            public void beforeTextChanged(CharSequence s, int start, int count, int after) {

            }

            @Override
            public void onTextChanged(CharSequence s, int start, int before, int count) {

            }

            @Override
            public void afterTextChanged(Editable s) {
                String thisString = s.toString();
                if (thisString.length() > 0 && !thisString.equals(lastString)) {
                    format();

                }
            }
        };

        addTextChangedListener(textWatcher);
    }


    private void format() {

        SpannableStringBuilder sb = new SpannableStringBuilder();
        String fullString = getText().toString();

        String[] strings = fullString.split(separator);


        for (int i = 0; i < strings.length; i++) {

            String string = strings[i];
            sb.append(string);

            if (fullString.charAt(fullString.length() - 1) != separator.charAt(0) && i == strings.length - 1) {
                break;
            }

            BitmapDrawable bd = (BitmapDrawable) convertViewToDrawable(createTokenView(string));
            bd.setBounds(0, 0, bd.getIntrinsicWidth(), bd.getIntrinsicHeight());

            int startIdx = sb.length() - (string.length());
            int endIdx = sb.length();

            sb.setSpan(new ImageSpan(bd), startIdx, endIdx, Spannable.SPAN_EXCLUSIVE_EXCLUSIVE);

            MyClickableSpan myClickableSpan = new MyClickableSpan(startIdx, endIdx);
            sb.setSpan(myClickableSpan, Math.max(endIdx-2, startIdx), endIdx, Spanned.SPAN_EXCLUSIVE_EXCLUSIVE);

            if (i < strings.length - 1) {
                sb.append(separator);
            } else if (fullString.charAt(fullString.length() - 1) == separator.charAt(0)) {
                sb.append(separator);
            }
        }


        lastString = sb.toString();

        setText(sb);
        setSelection(sb.length());

    }

    public View createTokenView(String text) {


        LinearLayout l = new LinearLayout(getContext());
        l.setOrientation(LinearLayout.HORIZONTAL);
        l.setBackgroundResource(R.drawable.bordered_rectangle_rounded_corners);

        TextView tv = new TextView(getContext());
        l.addView(tv);
        tv.setText(text);
        tv.setTextSize(TypedValue.COMPLEX_UNIT_DIP, 14);

        ImageView im = new ImageView(getContext());
        l.addView(im);
        im.setImageResource(R.drawable.ic_cross_15dp);
        im.setScaleType(ImageView.ScaleType.FIT_CENTER);

        return l;
    }

    public Object convertViewToDrawable(View view) {
        int spec = View.MeasureSpec.makeMeasureSpec(0, View.MeasureSpec.UNSPECIFIED);
        view.measure(spec, spec);
        view.layout(0, 0, view.getMeasuredWidth(), view.getMeasuredHeight());

        Bitmap b = Bitmap.createBitmap(view.getMeasuredWidth(), view.getMeasuredHeight(), Bitmap.Config.ARGB_8888);

        Canvas c = new Canvas(b);

        c.translate(-view.getScrollX(), -view.getScrollY());
        view.draw(c);
        view.setDrawingCacheEnabled(true);
        Bitmap cacheBmp = view.getDrawingCache();
        Bitmap viewBmp = cacheBmp.copy(Bitmap.Config.ARGB_8888, true);
        view.destroyDrawingCache();
        return new BitmapDrawable(getContext().getResources(), viewBmp);
    }

    private class MyClickableSpan extends ClickableSpan{

        int startIdx;
        int endIdx;

        public MyClickableSpan(int startIdx, int endIdx) {
            super();
            this.startIdx = startIdx;
            this.endIdx = endIdx;
        }

        @Override
        public void onClick(View widget) {



            String s = getText().toString();

            String s1 = s.substring(0, startIdx);
            String s2 = s.substring(Math.min(endIdx+1, s.length()-1), s.length() );

            TagEditText.this.setText(s1 + s2);
        }

    }
}

R.drawable.bordered_rectangle_rounded_corners:

<shape xmlns:Android="http://schemas.Android.com/apk/res/Android">
    <solid
        Android:color="@color/transparent"/>
    <stroke Android:width="1dp" Android:color="#AAAAAA" />
    <corners
        Android:radius="100dp" />
    <padding
        Android:left="5dp"
        Android:top="5dp"
        Android:right="5dp"
        Android:bottom="5dp" />
</shape>

最後に追加するのは、「xボタン」のpngです。これまでのところうまくいきますが、問題は、削除キーを長押ししても機能しないことです(誰かがそれを機能させる方法を考えている場合は、遠慮なくコメントしてください)。

8
AljoSt

私は良い解決策を見つけることができなかったので、これを処理するために独自のライブラリを構築します: TokenAutoComplete 。基本的な例は次のとおりです。

public class ContactsCompletionView extends TokenCompleteTextView {
    public ContactsCompletionView(Context context, AttributeSet attrs) {
        super(context, attrs);
    }

    @Override
    protected View getViewForObject(Object object) {
        Person p = (Person)object;

        LayoutInflater l = (LayoutInflater)getContext().getSystemService(Activity.LAYOUT_INFLATER_SERVICE);
        LinearLayout view = (LinearLayout)l.inflate(R.layout.contact_token, (ViewGroup)ContactsCompletionView.this.getParent(), false);
        ((TextView)view.findViewById(R.id.name)).setText(p.getName());

        return view;
    }

    @Override
    protected Object defaultObject(String completionText) {
        //Stupid simple example of guessing if we have an email or not
        int index = completionText.indexOf('@');
        if (index == -1) {
            return new Person(completionText, completionText.replace(" ", "") + "@example.com");
        } else {
            return new Person(completionText.substring(0, index), completionText);
        }
    }
}

Contact_tokenのレイアウトコード(独自のxドローアブルを見つける必要があります)

<LinearLayout xmlns:Android="http://schemas.Android.com/apk/res/Android"
    Android:layout_height="wrap_content"
    Android:layout_width="wrap_content"
    Android:background="@drawable/token_background">
    <TextView Android:id="@+id/name"
        Android:layout_width="wrap_content"
        Android:layout_height="wrap_content"
        Android:textColor="@Android:color/white"
        Android:textSize="14sp"
        Android:text="Test Me"
        Android:padding="2dp" />

    <ImageView
        Android:layout_height="10dp"
        Android:layout_width="10dp"
        Android:src="@drawable/x"
        Android:layout_gravity="center_vertical"
        Android:layout_marginLeft="3dp"
        Android:layout_marginRight="5dp" />
</LinearLayout>

トークンバックグラウンドドローアブル

<shape xmlns:Android="http://schemas.Android.com/apk/res/Android" >
    <solid Android:color="#ffafafaf" />
    <corners
        Android:topLeftRadius="5dp"
        Android:bottomLeftRadius="5dp"
        Android:topRightRadius="5dp"
        Android:bottomRightRadius="5dp" />
</shape>

人物オブジェクトコード

public class Person implements Serializable {
    private String name;
    private String email;

    public Person(String n, String e) { name = n; email = e; }

    public String getName() { return name; }
    public String getEmail() { return email; }

    @Override
    public String toString() { return name; }
}

サンプル活動

public class TokenActivity extends Activity {
    ContactsCompletionView completionView;
    Person[] people;
    ArrayAdapter<Person> adapter;

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

        people = new Person[]{
                new Person("Marshall Weir", "[email protected]"),
                new Person("Margaret Smith", "[email protected]"),
                new Person("Max Jordan", "[email protected]"),
                new Person("Meg Peterson", "[email protected]"),
                new Person("Amanda Johnson", "[email protected]"),
                new Person("Terry Anderson", "[email protected]")
        };

        adapter = new ArrayAdapter<Person>(this, Android.R.layout.simple_list_item_1, people);

        completionView = (ContactsCompletionView)findViewById(R.id.searchView);
        completionView.setAdapter(adapter);
        completionView.setTokenClickStyle(TokenCompleteTextView.TokenClickStyle.Delete);
    }
}

レイアウトコード

<RelativeLayout xmlns:Android="http://schemas.Android.com/apk/res/Android"
    Android:layout_width="match_parent"
    Android:layout_height="match_parent">

    <com.tokenautocomplete.ContactsCompletionView
        Android:id="@+id/searchView"
        Android:layout_width="match_parent"
        Android:layout_height="wrap_content" />

</RelativeLayout>
4
Marshall Weir