从 RecyclerView 中的添加项聚焦 EditText

Focus EditText from add item in RecycleView

我有一个应用程序,我在其中使用 RecycleViewCardViewsCardView 包含一个 EditText 现在当我向 RecycleView 添加一个新的 CardView 时,EditText 应该被聚焦并且键盘应该出现。

我怎样才能做到这一点?我试图在 onBindViewHolder:

中添加一些代码
public void onBindViewHolder(TodoViewHolder holder, final int position) {
    ...
    if(holder.tvDescription.requestFocus()) {
        window.setSoftInputMode(WindowManager.LayoutParams.SOFT_INPUT_STATE_ALWAYS_VISIBLE);
    }
    ...
}

或者虽然创建了 ViewHolder 但是没有成功。

public class TodoViewHolder extends RecyclerView.ViewHolder {
    protected CheckBox cbDone;
    protected EditText tvDescription;
    protected FloatingActionButton btnDelete;

    public TodoViewHolder(View itemView) {
        super(itemView);

        cbDone = (CheckBox)itemView.findViewById(R.id.cbDone);
        tvDescription = (EditText) itemView.findViewById(R.id.tvDescription);
        btnDelete = (FloatingActionButton) itemView.findViewById(R.id.btnDelete);

        if(tvDescription.requestFocus()) {
            window.setSoftInputMode(WindowManager.LayoutParams.SOFT_INPUT_STATE_ALWAYS_VISIBLE);
        }
    }
}

这是我的 AdapterCode 解决方案:

public abstract class ArrayAdapter<T, VH extends RecyclerView.ViewHolder>
        extends RecyclerView.Adapter<VH> {

    private static final String TAG = "CustomArrayAdapter";

    private List<T> mObjects;

    public ArrayAdapter(final List<T> objects) {
        mObjects = objects;
    }

    /**
     * Adds the specified object at the end of the array.
     *
     * @param object The object to add at the end of the array.
     */
    public void add(final T object) {
        mObjects.add(object);
        notifyItemInserted(getItemCount() - 1);
    }

    /**
     * Remove all elements from the list.
     */
    public void clear() {
        final int size = getItemCount();
        mObjects.clear();
        notifyItemRangeRemoved(0, size);
    }

    @Override
    public int getItemCount() {
        return mObjects.size();
    }

    public T getItem(final int position) {
        return mObjects.get(position);
    }

    public long getItemId(final int position) {
        return position;
    }

    public List<T> getItems() {
        return mObjects;
    }

    /**
     * Returns the position of the specified item in the array.
     *
     * @param item The item to retrieve the position of.
     * @return The position of the specified item.
     */
    public int getPosition(final T item) {
        return mObjects.indexOf(item);
    }

    /**
     * Inserts the specified object at the specified index in the array.
     *
     * @param object The object to insert into the array.
     * @param index  The index at which the object must be inserted.
     */
    public void insert(final T object, int index) {
        mObjects.add(index, object);
        notifyItemInserted(index);

    }

    /**
     * Removes the specified object from the array.
     *
     * @param object The object to remove.
     */
    public void remove(T object) {
        final int position = getPosition(object);
        remove(position);
    }

    public void remove(int position) {
        if (position < 0 || position >= mObjects.size()) {
            Log.e(TAG, "remove: index=" + position);
        } else {
            mObjects.remove(position);
            notifyItemRemoved(position);
        }
    }

    /**
     * Sorts the content of this adapter using the specified comparator.
     *
     * @param comparator The comparator used to sort the objects contained in this adapter.
     */
    public void sort(Comparator<? super T> comparator) {
        Collections.sort(mObjects, comparator);
        notifyItemRangeChanged(0, getItemCount());
    }
}

实施Adapter:

public class RecyclerViewAdapter extends ArrayAdapter<Todo, RecyclerViewAdapter.TodoViewHolder> {

    private static final String TAG = "RecyclerViewAdapter";
    private Todo selectedItem;
    private final Window window;

    public RecyclerViewAdapter(List<Todo> todos, Window window) {
        super(todos);
        this.window = window;
    }

    public Todo getSelectedItem() {
        return selectedItem;
    }

    public class TodoViewHolder extends RecyclerView.ViewHolder implements View.OnCreateContextMenuListener {
        protected CheckBox cbDone;
        protected EditText tvDescription;
        protected FloatingActionButton btnDelete;

        public TodoViewHolder(View itemView) {
            super(itemView);

            cbDone = (CheckBox)itemView.findViewById(R.id.cbDone);
            tvDescription = (EditText) itemView.findViewById(R.id.tvDescription);
            btnDelete = (FloatingActionButton) itemView.findViewById(R.id.btnDelete);

            itemView.setOnCreateContextMenuListener(this);
        }

        @Override
        public void onCreateContextMenu(ContextMenu menu, View v, ContextMenu.ContextMenuInfo menuInfo) {
            menu.setHeaderTitle("Send to:");
            menu.add(0, v.getId(), 0, "all");

            Log.d(TAG, "view id: " + v.getId());
        }
    }

    @Override
    public void add(Todo object) {
        object.shouldBeFocused = true;
        super.add(object);
    }

    @Override
    public TodoViewHolder onCreateViewHolder(ViewGroup viewGroup, int viewType) {
        View view = LayoutInflater.from(viewGroup.getContext()).inflate(R.layout.todo_layout, viewGroup, false);
        return new TodoViewHolder(view);
    }

    @Override
    public void onBindViewHolder(final TodoViewHolder holder, final int position) {
        final Todo todo = getItem(holder.getAdapterPosition());
        holder.cbDone.setChecked(todo.isChecked);
        holder.tvDescription.setText(todo.description);

        holder.tvDescription.addTextChangedListener(new TextWatcher() {
            @Override
            public void beforeTextChanged(CharSequence s, int start, int count, int after) {
                // Do nothing
            }

            @Override
            public void onTextChanged(CharSequence s, int start, int before, int count) {
                // Do nothing
            }

            @Override
            public void afterTextChanged(Editable s) {
                todo.description = s.toString();
            }
        });

        holder.cbDone.setOnCheckedChangeListener(new CompoundButton.OnCheckedChangeListener() {
            @Override
            public void onCheckedChanged(CompoundButton buttonView, boolean isChecked) {
                Log.i(TAG, "onCheckedChanged called: isDone=" + isChecked);
                todo.isChecked = isChecked;
            }
        });

        holder.btnDelete.setOnClickListener(new View.OnClickListener() {
            @Override
            public void onClick(View v) {
                Log.i(TAG, "onClick called: remove todo.");
                remove(todo);
            }
        });

        View.OnLongClickListener onClickListener = new View.OnLongClickListener() {
            @Override
            public boolean onLongClick(View v) {
                selectedItem = todo;
                return false;
            }
        };

        holder.cbDone.setOnLongClickListener(onClickListener);
        holder.tvDescription.setOnLongClickListener(onClickListener);
        holder.btnDelete.setOnLongClickListener(onClickListener);

        if (todo.shouldBeFocused) {
            holder.tvDescription.post(new Runnable() {
                @Override
                public void run() {
                    if (holder.tvDescription.requestFocus()) {
                        window.setSoftInputMode(WindowManager.LayoutParams.SOFT_INPUT_STATE_ALWAYS_VISIBLE);
                        InputMethodManager inputMethodManager = (InputMethodManager) holder.tvDescription.getContext().getSystemService(Context.INPUT_METHOD_SERVICE);
                        inputMethodManager.showSoftInput(holder.tvDescription, InputMethodManager.SHOW_IMPLICIT);
                    }
                }
            });
            todo.shouldBeFocused = false;
        }
    }
}

Todo:

public class Todo implements Serializable {

    // The creationDate is not used at the moment
    protected Date creationDate;
    protected String description;
    protected boolean isChecked;
    protected boolean shouldBeFocused;

    public Todo(String description) {
        this.description = description;
        this.creationDate = new Date();
    }

    public Date getCreationDate() {
        return creationDate;
    }

    public String getDescription() { return description; }

    @Override
    public String toString() {
        return creationDate + ": " + description + " state[" + isChecked + "]";
    }
}

并在 MainActivity 添加方法:

FloatingActionButton fab = (FloatingActionButton) findViewById(R.id.fab);
fab.setOnClickListener(new View.OnClickListener() {
    @Override
    public void onClick(View view) {
        adapter.add(new Todo(""));
        int count = adapter.getItemCount();
        recyclerView.smoothScrollToPosition(count - 1);
    }
});

测试某些解决方案时出现问题:

这样试试:

@Override
public void onBindViewHolder(final ViewHolder holder, int position) {

    ...

    // If this ViewHolder needs keyboard focus, execute below code.
    holder.itemView.getViewTreeObserver().addOnGlobalLayoutListener(new ViewTreeObserver.OnGlobalLayoutListener() {
        @Override
        public void onGlobalLayout() {
            if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.JELLY_BEAN) {
                holder.itemView.getViewTreeObserver().removeOnGlobalLayoutListener(this);
            } else {
                holder.itemView.getViewTreeObserver().removeGlobalOnLayoutListener(this);
            }

            EditText editText = holder.tvDescription;
            editText.requestFocus();
            InputMethodManager imm = (InputMethodManager) editText.getContext().getSystemService(Context.INPUT_METHOD_SERVICE);
            imm.showSoftInput(editText, 0);
        }
    });
}

问题是因为您调用 requestFocus() 太早,导致您的视图尚未出现在屏幕上。此外,当您添加新元素时,您还应该添加一些标志 - 您是否请求关注此视图,以防止 RecyclerView 中的所有先前视图都被关注。假设你在RecyclerView的末尾添加了一个新的CardView,那么你的Adapter的add方法应该是这样的:

public void addToEnd(Model item) {
    item.shouldBeFocused = true;
    dataset.add(item);
    notifyItemInserted(dataset.size() - 1);
}

然后在你的 onBindViewHolder() 中做这样的事情:

Model item = dataset.get(position);
...
if (item.shouldBeFocused) {
    holder.tvDescription.post(new Runnable() {
        @Override
        public void run() {
            if (holder.tvDescription.requestFocus()) {
                window.setSoftInputMode(WindowManager.LayoutParams.SOFT_INPUT_STATE_ALWAYS_VISIBLE);
                InputMethodManager inputMethodManager = (InputMethodManager) holder.tvDescription.getContext().getSystemService(Context.INPUT_METHOD_SERVICE);
                inputMethodManager.showSoftInput(holder.tvDescription, InputMethodManager.SHOW_IMPLICIT);
            }
        }
    });
    item.shouldBeFocused = false;
}
...

此外,您可能需要滚动到 RecyclerView 的最后位置才能为新添加的元素调用 onBindViewHolder()。例如,您可以通过 setStackFromEnd = true 行来执行此操作。

更新:

你的问题是你在 onBindViewHolder 方法中添加了 TextWatcher,首先它是非常昂贵的操作,其次你将输入的文本保存到 final 引用,那是为什么你的 RecyclerView 之后给出了不合适的结果。

因此,尝试创建您的自定义 TextWatcher,将当前项目的位置保持在 Adapter first:

private static class PositionTextWatcher implements TextWatcher {
    private int position;

    public void updatePosition(int position) {
        this.position = position;
    }

    @Override
    public void beforeTextChanged(CharSequence charSequence, int i, int i2, int i3) {
        // no op
    }

    @Override
    public void onTextChanged(CharSequence charSequence, int i, int i2, int i3) {
        final Todo todo = getItem(position);
        todo.description = charSequence.toString();
    }

    @Override
    public void afterTextChanged(Editable editable) {
        // no op
    }
}

然后将其添加到ViewHolder的构造函数中的EditText,当onCreateViewHolder被调用时:

@Override
public TodoViewHolder onCreateViewHolder(ViewGroup viewGroup, int viewType) {
    View view = LayoutInflater.from(viewGroup.getContext()).inflate(R.layout.todo_layout, viewGroup, false);
    return new TodoViewHolder(view, new PositionTextWatcher());
}

public class TodoViewHolder extends RecyclerView.ViewHolder implements View.OnCreateContextMenuListener {
    protected CheckBox cbDone;
    protected EditText tvDescription;
    protected FloatingActionButton btnDelete;
    protected PositionTextWatcher positionTextWatcher;

     public TodoViewHolder(View itemView, PositionTextWatcher positionTextWatcher) {
        super(itemView);

        cbDone = (CheckBox)itemView.findViewById(R.id.cbDone);
        tvDescription = (EditText) itemView.findViewById(R.id.tvDescription);
        btnDelete = (FloatingActionButton) itemView.findViewById(R.id.btnDelete);
        this.positionTextWatcher = positionTextWatcher;
        tvDescription.addTextChangedListener(this.positionTextWatcher);
        itemView.setOnCreateContextMenuListener(this);
    }

    @Override
    public void onCreateContextMenu(ContextMenu menu, View v, ContextMenu.ContextMenuInfo menuInfo) {
        menu.setHeaderTitle("Send to:");
        menu.add(0, v.getId(), 0, "all");

        Log.d(TAG, "view id: " + v.getId());
    }
}

最后 ,而不是每次在 onBindViewHolder() 中添加一个新的 TextWatcher,只需更新您的自定义 TextWatcher 中的位置:

@Override
public void onBindViewHolder(final TodoViewHolder holder, final int position) {
    final Todo todo = getItem(holder.getAdapterPosition());
    ...
    holder.cbDone.setChecked(todo.isChecked);
    holder.positionTextWatcher.updatePosition(position);
    holder.tvDescription.setText(todo.description);
    ...
}

这应该很有魅力!从 获得了该解决方案,因此请检查它以了解更多背景信息。