I have got an app where I use a RecycleView
with CardViews
. The CardView
contains an EditText
now when I add a new CardView
to the RecycleView
the EditText
should be focused and the keyboard should appear.
How can I achieve that? I have tried to add some code in the onBindViewHolder
:
public void onBindViewHolder(TodoViewHolder holder, final int position) {
...
if(holder.tvDescription.requestFocus()) {
window.setSoftInputMode(WindowManager.LayoutParams.SOFT_INPUT_STATE_ALWAYS_VISIBLE);
}
...
}
Or while the creation of the ViewHolder
but it hasn't worked.
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);
}
}
}
Here is my AdapterCode
with a solution:
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());
}
}
The implemented 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;
}
}
}
The 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 + "]";
}
}
And in the MainActivity
the add method:
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);
}
});
Problem when testing some solution:
The problem is because you call requestFocus()
too early, cause your view doesn't appear on the screen yet. Also you should add some flag, when you're adding a new element - should you request focus on this view or not, to prevent all previous views in RecyclerView
be focused. Assuming you add a new CardView
to the end of RecyclerView
, so your add method of Adapter
should be like this:
public void addToEnd(Model item) {
item.shouldBeFocused = true;
dataset.add(item);
notifyItemInserted(dataset.size() - 1);
}
And then in your onBindViewHolder()
do something like this:
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;
}
...
Also you would probably need to scroll to the last position of your RecyclerView
to call your onBindViewHolder()
for the new added element. You can do this for example by setStackFromEnd = true line.
UPDATE:
Your problem is that you're adding TextWatcher
inside onBindViewHolder
method, firstly it's very expensive operation, and secondly you're saving entered text to the final
reference, that's why your RecyclerView
gives inappropriate results after.
So, try to create your custom TextWatcher
, that keeps position of current item in 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
}
}
Then add it to your EditText
in ViewHolder
's constructor, when onCreateViewHolder
will be called:
@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());
}
}
And finally, instead of adding a new TextWatcher
each time in onBindViewHolder()
, just update the position in your custom 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);
...
}
This should work like a charm! Got that solution from this perfect answer, so check it for more background.
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With