RecyclerView scroll to position of child view's child - java

I want to scroll to the position in the picture:
Here is my code but there is NullPointerException
public class ViewMenu extends LinearLayout {
protected Handler mHandler;
#BindView(R.id.recycler)
protected RecyclerView mRecycler;
//...
#Override
protected void onFinishInflate() {
super.onFinishInflate();
ButterKnife.bind(this);
mHandler = new Handler();
mRecycler.setLayoutManager(new LinearLayoutManager(getContext(), LinearLayoutManager.HORIZONTAL, false));
//...
Message msg=new Message();
msg.what=1;
Bundle bundle=new Bundle();
bundle.putInt("targetPosition",3);//assume this is a valid position
bundle.putInt("targetChild",2);//assume this is a valid child
msg.setData(bundle);
handler.sendMessage(msg);
}
class Handler extends android.os.Handler {
#Override
public void handleMessage(Message msg) {
int targetPosition=msg.getData().getInt("targetPosition");
int targetChild=msg.getData().getInt("targetChild");
LinearLayoutManager layoutManager = (LinearLayoutManager) mRecycler.getLayoutManager();
ViewGroup targetViewGroup = (ViewGroup) layoutManager.findViewByPosition(targetPosition);//targetViewGroup becomes null
View targetView = targetViewGroup.getChildAt(targetChild);//NullPointerException
layoutManager.scrollToPositionWithOffset(targetPosition, targetView.getLeft());
}
}
}
I think the problem is that when the targetViewGroup is invisible findViewByPosition returns null. Can anyone find a better way to do that?

Finally I solved it myself. The cause while I guess, is when you call layoutManager.scrollToPositionWithOffset() method, a scroll event is sent to the handler. The recycler view will not do a real scroll until the looper got the message. So the proper way is use handler.post() after calling scrollToPositionWithOffset().
LinearLayoutManager layoutManager = (LinearLayoutManager) mRecycler.getLayoutManager();
//scroll to make the target page visible
if (page < layoutManager.findFirstVisibleItemPosition())
//if the target page is in left and invisible, post: scroll to show one pixel in the screen left
layoutManager.scrollToPositionWithOffset(page + 1, 1);
else if (page > layoutManager.findLastVisibleItemPosition())
//if the target page is in right and invisible, post: scroll to show one pixel in the screen right
layoutManager.scrollToPositionWithOffset(page, mRecycler.getWidth() - mRecycler.getPaddingLeft() - mRecycler.getPaddingRight() - 1);
//post: scroll to candidate
getHandler().post(() -> {
ViewGroup pageView = (ViewGroup) mRecycler.findViewHolderForAdapterPosition(page).itemView;
View candView = pageView.getChildAt(cand);
layoutManager.scrollToPositionWithOffset(page, left ? candView.getLeft() : candView.getRight());
});

Related

RelativeLayout addRule at BaseAdapter doesn't work very well

After one of rule is added to RelativeLayout, I get a don't very correct result. ID does not apply to element/is applied incorrectly. This affects at rules that must be bound to widget.
As a container, I use ListView and add objects to it through BaseAdapter. This obviously does not give desired result, but after reusing same widget that is returned back to the adapter and reused (element is guaranteed not changing again), rules starting working correctly.
#Override
protected void onCreate(Bundle state) {
super.onCreate(state);
ListView view = new ListView(this);
view.setBackgroundColor(Color.BLACK);
view.setId("files_list".hashCode());
view.setAdapter(new TestAdapter(this));
...
private class TestAdapter extends BaseAdapter {
...
#Override
public View getView(int position, View convertView, View parent) {
if(convertView == null) convertView = inflateView();
// only changes text & pictures, doesn't affecting display
manipulateItem(position, convertView);
return convertView;
...
private View inflateView() {
RelativeLayout layout = new RelativeLayout(context);
layout.setLayoutParams(new ViewGroup.LayoutParams(-1, -2));
ImageView icon = new ImageView(context);
icon.setBackgroundColor(Color.BLACK);
icon.setPadding(20, 20, 20, 20);
icon.setId("file_icon".hashCode());
RelativeLayout.LayoutParams params = new RelativeLayout.LayoutParams(110, -1);
params.addRule(RelativeLayout.ALIGN_PARENT_LEFT);
params.rightMargin = 30;
layout.addView(icon, params);
...
LinearLayout additional = new LinearLayout(context);
additional.setOrientation(LinearLayout.VERTICAL);
additional.setGravity(Gravity.RIGHT);
additional.setBackgroundColor(Color.RED);
additional.setPadding(30, 0, 30, 0);
additional.setId(java.lang.String("additional_info").hashCode());
RelativeLayout.LayoutParams params = new RelativeLayout.LayoutParams(-2, -2);
params.addRule(RelativeLayout.ALIGN_PARENT_RIGHT);
params.addRule(RelativeLayout.CENTER_IN_PARENT);
layout.addView(additional, params);
...
LinearLayout uniqal = new LinearLayout(context);
uniqal.setOrientation(LinearLayout.VERTICAL);
uniqal.setBackgroundColor(Color.BLUE);
uniqal.setId("uniqal_info".hashCode());
RelativeLayout.LayoutParams params = new RelativeLayout.LayoutParams(-2, -2);
// problem occurs here
params.addRule(RelativeLayout.LEFT_OF, additional.getId());
params.addRule(RelativeLayout.RIGHT_OF, icon.getId());
params.addRule(RelativeLayout.CENTER_IN_PARENT);
layout.addView(uniqal, params);
...
return layout;
}
This is what widget will looks like after first scrolling: elements after first drawing screen
And so, after scrolling and reusing same widget: working screen
After firstly 5 uses (so much got into my test screen), everything becomes fine. For the first time, views don't want to be attached by ID to another widget in any way. Is there a way around this?
Options for inflating layout from application .xml don't suit me
Manual update of layout helped me, however this doesn't work right away and my question remains relevant. Here is modified inflateView code:
private View inflateView() {
...
RelativeLayout.LayoutParams params = new RelativeLayout.LayoutParams(-2, -2);
params.addRule(RelativeLayout.LEFT_OF, additional.getId());
params.addRule(RelativeLayout.RIGHT_OF, icon.getId());
params.addRule(RelativeLayout.CENTER_IN_PARENT);
uniqal.post(new Runnable() {
public void run() {
uniqal.requestLayout();
}
});
layout.addView(uniqal, params);
...
return layout;
}

Android ListView Adaptor changing colour when you scroll past the item

Okay so we have some realm results returning a set of stock orders that we have made. Depending on if the stock order is fully checked in we are showing the background colour of the row item as green or blue using an if statement in the adaptor.
The issue is that on load the orders display as green/blue correctly. But when you scroll down the blue ones are changing to green. (See Images). We think it may be that the data is getting changed somewhere but not sure why this would be happening as we are just scrolling on the page.
Code that creates this:
StockOrdersFragment.java
#Nullable
#Override
public View onCreateView(LayoutInflater inflater, #Nullable ViewGroup container, Bundle savedInstanceState) {
myView = inflater.inflate(R.layout.stock_orders_layout, container, false);
listView = myView.findViewById(R.id.listView);
stockOrdersService = new StockOrdersService(this);
stockOrdersService.fetchFromServer(getActivity());
fetchStockOrders();
return myView;
}
public void fetchStockOrders()
{
stockOrders.clear();
RealmResults<StockOrder> savedStockOrders = stockOrdersService.all();
stockOrders.addAll(savedStockOrders);
StockOrdersAdaptor adaptor = new StockOrdersAdaptor(stockOrders, getActivity());
listView.setAdapter(adaptor);
}
#Override
public void ordersReceived() {
fetchStockOrders();
}
StockOrdersAdaptor.java (The bit that does the colour )
lastPosition = position;
if(stockOrder != null){
viewHolder.stockOrderId.setText(String.valueOf(stockOrder.id));
viewHolder.supplierName.setText(stockOrder.supplier.name);
if (stockOrder.allItemsHaveBeenReceived()) {
convertView.setBackgroundResource(R.color.colorGreen);
}
}
// Return the completed view to render on screen
return convertView;
The allItemsHaveBeenRecieved()
public Boolean allItemsHaveBeenReceived()
{
for (StockOrderDetails detail: details) {
if (detail.quantity != detail.quantityReceived) {
return false;
}
}
return true;
}
Add else statement here:
if (stockOrder.allItemsHaveBeenReceived()) {
convertView.setBackgroundResource(R.color.colorGreen);
} else {
convertView.setBackgroundResource(R.color.colorBlue);
}
Problem is when your item gets recycled it remains dirty. Make sure you are populating all UI items from your list item layout every time adapter binds to it.

Scrolling a ScrollView to a specific id

I have a Scrollview in my activity , I want when I press a button the view scrolls down to a specific ID in the view. After some search here I found I can use this code
scrollView.post(new Runnable() {
#Override
public void run() {
scrollView.fullScroll(View.FOCUS_DOWN);
}
});
but it scrolls all the page down. I want to scroll to a specific ID in the middle of the view
ScrollView scrollView = (ScrollView) findViewById(R.id.scroll_view);
scrollView.postDelayed(new Runnable() {
public void run() {
scrollView.scrollTo(0, (int)view.getY());
}
}, 100);
view = your view you want to scroll to
You can do this as below:
private void smartScrollToPosition(ListView listView, int desiredPosition) {
//Write your logic here.
listView.smoothScrollToPosition(desiredPosition);
}
On button handler you can call this as follows
ListView listView = (ListView) findViewById(R.id.lessonsListView);
smartScrollToPosition(listView, 0); // scroll to top
return true;

How to change background of non-visible buttons in GridView (Button adapter) from an activity

My code works only for visible views.
mGridView = (GridView) findViewById(R.id.gridView);
mGridView.setAdapter(new ButtonAdapter(this, list));
Method called when chronometer ticks:
public void setBackground(int i) {
Button button = (Button) mGridView.getChildAt(i);
button.setBackgroundResource(R.drawable.button_shape);
This method causes NPE because getChildAt cannot access non-visible childs.
I tried some solutions found here but no luck so far (android - listview get item view by position - this solution did not causes NPE but was changing background for more buttons in one tick)
What I need is to change first button background on first tick, second button on second tick.. and last button on last tick and stay consistent while scrolling.
My getView method in ButtonAdapter:
public View getView(final int position,
View convertView, ViewGroup parent) {
Button btn;
LayoutInflater inflater = LayoutInflater.from(mContext);
if (convertView == null) {
// if it's not recycled, initialize some attributes
btn = (Button) inflater.inflate(R.layout.button, parent, false);
int width = mContext.getResources().getDimensionPixelSize(R.dimen.gridView_param_width);
int height = mContext.getResources().getDimensionPixelSize(R.dimen.gridView_param_height);
GridView.LayoutParams params = new GridView.LayoutParams(width, height);
btn.setLayoutParams(params);
} else {
btn = (Button) convertView;
}
btn.setText(list.get(position).getName());
btn.setId(position);
btn.setTag(position);
btn.setOnClickListener(new View.OnClickListener() {
#Override
public void onClick(View v) {
SetGridViewListener activity = (SetGridViewListener) mContext;
activity.onClickGridView(position);
Button btn = (Button)v;
btn.setBackgroundResource(R.drawable.button_shape_clicked);
btn.setClickable(false);
}
});
return btn;
}
I think my problem is in getView method which is probably not recycling well for my purpose.
Thanks in advance.
I have solved it already. I used ViewHolder pattern like here ListView subobject clickable confilct and this method to access buttons from my activity:
public View getViewByPosition(int pos, GridView gridView) {
final int firstListItemPosition = listView.getFirstVisiblePosition();
final int lastListItemPosition = firstListItemPosition + listView.getChildCount() - 1;
if (pos < firstListItemPosition || pos > lastListItemPosition ) {
return gridView.getAdapter().getView(pos, null, listView);
} else {
final int childIndex = pos - firstListItemPosition;
return gridView.getChildAt(childIndex);
}
}
(android - listview get item view by position)
If you would like my concrete solution, write in comment and I will add it.

Runnable changes wrong cell

I have got a list view with 9 rows in it. Every row has two TextViews and a ImageButton which plays a song specific for that row. If it is playing one of the two TextViews should change color and change the text every second to get a result like '1:12 - 7:35'. And that's where my problem lies.
The first time the list view loads all elements that are on screen work fine but whenever I scroll down, tap on the playButton it highlights the wrong cell. Probably because list view's position returns the position on the screen and not the position in the list.
private MediaPlayer mp;
private Handler handler;
private int playingCellPosition = -999;
public View getView(final int position, View convertView, final ViewGroup parent) {
// Find the oefening to work with
final Oefening currentExercise = myExercises.get(position);
LayoutInflater vi = (LayoutInflater)getSystemService(Context.LAYOUT_INFLATER_SERVICE);
final View itemView = vi.inflate(R.layout.cell, null);
// Get textviews
final TextView durationTextView = (TextView) itemView.findViewById(R.id.cell_duur);
final Runnable updateDurationTextView = new Runnable() {
#Override
public void run() {
runOnUiThread(new Runnable(){
public void run(){
Oefening playingExercise = currentExercise.get(playingCellPosition);
TextView durationTextViewToUpdate = (TextView) parent.getChildAt(playingCellPosition).findViewById(R.id.cell_duur);
durationTextViewToUpdate.setText(getFormattedCurrent(mp.getCurrentPosition()) + " - " + playingExercise.getDuration());
durationTextViewToUpdate.setTextColor(Color.parseColor("#b71393"));
}
});
handler.postDelayed(this, 1000);
}
};
ImageButton playButton = (ImageButton) itemView.findViewById(R.id.cell_playButton);
playButton.setOnClickListener(new OnClickListener() {
public void onClick(final View v) {
if (mp.isPlaying()) {
// Pause it here. Not very important to this problem since it occurs when it starts playing and not when it stops
mp.pause();
v.setBackgroundResource(R.drawable.play_icon);
durationTextView.setText(currentExercise.getDuration());
durationTextView.setTextColor(Color.BLACK);
handler.removeCallbacks(updateDurationTextView);
} else {
mp = MediaPlayer.create(MainActivity.this, currentExercise.getAudioFile());
mp.start();
v.setBackgroundResource(R.drawable.pause_icon);
handler.post(updateDurationTextView);
}
}
});
}
The Oefening playingExercise = currentExercise.get(playingCellPosition); works fine though, since it shows the information of the cell whose play button I tapped on. It just shows the information on the wrong cell.
As Áron Nemmondommegavezetéknevem pointed out, the problem is in parent.getChildAt(...);
Note 1: I don't reuse views with convertView since that messed up positions a lot. This is the closest I have come to what I have to achieve.
Note 2: I left out a lot of the code for the MediaPlayer. It is constructed well, so don't worry about that.
Note 3: If someone has a better suggestion for a title, please edit this one. Couldn't come up with a better one.
The problem is obviously with
parent.getChildAt(...);
In the case of a ListView it doesn't return the view of the specific position. Actually it's quite unpredictable what it returns with.
To illustrate why it doesn't return with the view of the specified position: imagine a list view with 10000 or more items. ListView has only a few views, and doesn't have views for all the 10000 items. What could it return with if you would request the 2786. view? Nothing, it doesn't have a view for that item.
Edit:
Suggestion:
Although I don't see how your code works, you should store which item view belongs to an individual item. For example, you can call setTag(position) on convertView before you return with it. Then you can write a function which finds the appropriate view for an item, if it exists. Something similar to this:
public View findViewAtPosition(int position) {
for (int i=0; i < listView.getChildCount(); i++) {
if (listView.getChildAt(i).getTag() == position) {
return(listView.getChildAt(i));
}
}
return(null);
}
I fixed it with an answer on another topic: https://stackoverflow.com/a/2679284/1352169
Áron Nemmondommegavezetéknevem's answer looks good, but this one is a little bit better since it e.g. keeps headers in mind too.

Categories

Resources