How to properly change elements within Fragments - java

I have two pages (Fragments) in my app that each have their own interaction. A long click on either page should toggle the color scheme to black and white and back again. This is accomplished using a separate java class that is called from either page. This works perfectly when run from the second page, but when run from the first page, the second page isn't changed. Here's some of my code, simplified for length:
Page 1:
firstView = inflater.inflate(R.layout.page1, container, false);
secondView = inflater.inflate(R.layout.page2, container);
//A bunch of code and then....
element1.setOnLongClickListener(new View.OnLongClickListener() {
#Override
public boolean onLongClick(View view) {
pm = sharedPreferences.getBoolean("pm", false);
if (pm) {
performance.PerfModeOff();
pm = false;
sharedPreferences.edit().putBoolean("pm", pm).apply();
} else {
performance.PerfModeOn(mContext);
pm = true;
sharedPreferences.edit().putBoolean("pm", pm).apply();
}
return true;
}
});
Page 2:
secondView= inflater.inflate(R.layout.page2, container, false);
firstView = inflater.inflate(R.layout.page1, container);
//A bunch of code and then...
element2.setOnLongClickListener(new View.OnLongClickListener() {
#Override
public boolean onLongClick(View view) {
pm = sharedPreferences.getBoolean("pm", false);
if (pm) {
performance.PerfModeOff();
pm = false;
sharedPreferences.edit().putBoolean("pm", pm).apply();
} else {
performance.PerfModeOn(mContext);
pm = true;
sharedPreferences.edit().putBoolean("pm", pm).apply();
}
return true;
}
});
The class that's changing the colors:
public Performance(View firstView, View secondView,LayoutInflater inflater, ViewGroup container) {
mfirstView = inflater.inflate(R.layout.page1, container,false);
msecondView = inflater.inflate(R.layout.page2, container,true);
uparrow= (ImageView) firstView.findViewById(R.id.UpArrow);
//**A lot of lines like above
public void PerfModeOn(Context context) {
mfirstView.setBackgroundColor(Color.BLACK);
msecondView.setBackgroundColor(Color.BLACK);
uparrow.setBackgroundTintList(ColorStateList.valueOf(context.getResources().getColor(R.color.DarkTint)));
//** More lines to change colors
public void PerfModeOff() {
//**Lines to change colors back
Of course if you need to see some more code, just let me know. What can I do to fix this? Thanks for your help!

Your second view never changes because PerfModeOn() calls mfirstView.setBackgroundColor(Color.BLACK); naively. You need to pass which view to set the background of, so it sets the correct background.
In other words, your first view will only ever be the view changed because of how explicit you were when setting the background.
An alternative would be:
public void perfModeOn(Context context, View viewToSetBackgroundOf) {
viewToSetBackgroundOf.setBackgroundColor(Color.BLACK);
...
}
An even better approach would be to implement an OnLongClickListener that both of your views use. Then you get which view was clicked in your onLongClick() method. Then a simple if statement on which view was clicked, and set the background of the opposite view. This would get rid of your redundant code, and some unneeded methods.

Related

How to make the holder in if else statements of onClick method work without final in argument Viewholder in arguments of onBindViewHolder method

I am trying to make a text change when a button located along with the text (layoutPasswd) in recycler view and to change it back if the button is again pressed.Like a password hiding button. The values to the adapter is from a static class object as arraylist. The problem occurring now is that the value for all the items (only for layoutPasswd) in recycler view is same.
public void onBindViewHolder(#NonNull final viewHolder holder, int position) {
holder.layoutUName.setText(users.get(position).getUserName());
pos = position;
holder.layoutPasswd.setText("********");
holder.btnViewChanger.setOnClickListener(new View.OnClickListener() {
#Override
public void onClick(View v) {
if (holder.view1) {
holder.layoutPasswd.setText(users.get(pos).getPasswd());
holder.btnViewChanger.setText("hide");
holder.view1 = false;
} else {
holder.layoutPasswd.setText("********");;
holder.btnViewChanger.setText("Show");
holder.view1 = true;
}
}
});
You cannot rely on the ViewHolders or Views in a RecyclerView to hold any state, because they are recycled. Every time a view scrolls onto the screen, first it calls your onBindViewHolder function to update the contents of that ViewHolder to match the data.
Any configuration you set on the views or the ViewHolder instance in onBindViewHolder cannot be relied on to stay the same if the view scrolls off the screen, because the original ViewHolder might be recycled to be used for some other data, and when it scrolls back on screen, you might be looking at some other view that has been recycled from other data that just scrolled off the screen.
So if your views have configuration that you want to "stick", you have to back it up when you change it, and restore it in onBindViewHolder. The way you accomplish this will depend on how you are managing the data that you pass to the adapter.
If you can modify your User class, you can add a Boolean to it that stores whether it should show the password. Then in your onBindViewHolder, you restore the state based on this Boolean. And you also update this Boolean when the state changes.
I also updated the way the click listener works to simplify it for toggling. I removed the pos = position line, because almost certainly that is not something you should be doing.
public void onBindViewHolder(#NonNull final viewHolder holder, int position) {
final User user = users.get(position)
holder.layoutUName.setText(user.getUserName());
holder.layoutPasswd.setText(user.isShowPassword() ? user.getPasswd() : "********");
holder.btnViewChanger.setOnClickListener(new View.OnClickListener() {
#Override
public void onClick(View v) {
user.setShowPassword(!user.isShowPassword());
holder.layoutPasswd.setText(user.isShowPassword() ? user.getPasswd() : "********");
holder.btnViewChanger.setText(user.isShowPassword() ? "hide" : "show");
}
});
// ...
}
If you cannot modify the User class, this is more complicated. Then the adapter should have its own ArrayList<Boolean> to store the state by position index, but you need to keep this list at least as long as the data that is bound, and reset everything to false if the whole list of data is refreshed.

Android - SwitchCompact disable/stop default behaviour

In my android project I have SwitchCompact, onClick of which I do networking stuff and on basis response I want to change SwitchCompact to checked or not checked.
The problem I am facing now is :
When I click on SwitchCompact it changes its status (ie true to false and vice versa) which is its normal behaviour. I want to disable its normal/default behaviour.
Is that possible?
class MyClass extends Fragment implements View.OnClickListener {
SwitchCompat switchCompact;
#Nullable
#Override
public View onCreateView(LayoutInflater inflater, ViewGroup container, Bundle savedInstanceState) {
//my view inflating stuff
switchCompact= (SwitchCompat) view.findViewById(R.id.switch_id);
switchCompact.setOnClickListener(this);
return view;
}
#Override
public void onClick(View view) {
if (view.getId()==R.id.switch_id) {
//make http request
}
}
public void onResponse(Response response){
switchCompact.setChecked(response.getStatus());
}
}
Any help will be appreciated.
Well putting more pressure on my head I found something like work around, not an exact answer :)
#Override
public void onClick(View view) {
if (view.getId()==R.id.switch_id) {
switchCompact.setChecked(!switchCompact.isChecked());
//make networking stuff.
}
}
#RageshRamesh : I was thinking why not revert back its status before response or after onClick i.e. when the Switch changes its status. This workaround works as expected. Thanks for your help.

Adding a Button on a View

I am going nuts over this.
I did not find any working solution (tried a few from stackoverflow)
Scenario (this is an actual screenshot what is already done):
I have a Activity that has a View as his Attribute.
This view adds another view via View.addView(myView).
I now want to add a Button to myView (to be specific: after MotionEvent.ACTION_UP the button should appear in the right lower corner (this will start the robot to drive the track))
Here is a shortcut of my code:
public class ModeRouting extends View {
public ModeRouting(Context context) {
super(context);
Button asuroStartButton = new Button(context) //does not work
}
#Override
public boolean onTouchEvent(MotionEvent event) {
int actionevent = event.getAction();
if (actionevent == MotionEvent.ACTION_UP
|| actionevent == MotionEvent.ACTION_CANCEL) {
asuroStartButton.visible=true;
view.add(asuroStartButton);
}
return true;
}
}
and my Activity:
//in constructor
contentView = (FrameLayout) findViewById(R.id.content);
onClickListenerFacade(routingMode, route);
//this removes all views from stack and places the new one on the view
private void onClickListenerFacade(View v, final View target) {
v.setOnClickListener(new View.OnClickListener() {
#Override
public void onClick(View arg0) {
contentView.removeAllViews();
contentView.setBackgroundColor(0xff000000);
contentView.addView(target);
modeSelectorAnimation();
}
});
}
I tried to create a button in my mainactivity.xml and instantiate in my mainactivity.
I am missing some point in here but i am not sure which.
Since my view is purely dynamic (no layout.xml) i dont think i should use a layout.xml (maybe thats my mind-blockage) but instead set the button attributes dynamically too.
Any hint is appreciated!
You want to extend ViewGroup rather than just a View (LinearLayout, RelativeLayout, FrameLayout, etc) - they handle child views for you.
I think maybe you need to refresh the whole view/activity. Try to do this in the onResume methode, maybe this helps. But as you don't use a layout.xml, I'm not sure if this helps you much..
#Override
protected void onResume(){
super.onResume();
setContentView(R.layout.activity_main);
}

Drag and Drop in a `ListView`

I am trying to implement a drag and drop in a ListView in android(Ice Cream Sandwich). So when the dragged object reaches the edge of the ListView, I am scrolling the ListView in the relevant direction. The problem is that when we scroll, sometimes the adapter creates new Views as necessary and these 'new' Views did not receive the ACTION_DRAG_STARTED event earlier and hence do not receive the DragEvent updates. Is there any way I can send the events to these views as well?
An easiest way to implement drag and drop in listview is you use this great library.
https://github.com/commonsguy/cwac-touchlist
it's worth trying.
Looking at the source for View, I see:
static final int DRAG_CAN_ACCEPT = 0x00000001;
int mPrivateFlags2;
boolean canAcceptDrag() {
return (mPrivateFlags2 & DRAG_CAN_ACCEPT) != 0;
}
mPrivateFlags2 is package-private and not exposed by the SDK. However, you should be able to change it in a subclass by doing:
try {
Field mPrivateFlags2 = this.getClass().getField("mPrivateFlags2");
int currentValue = mPrivateFlags2.getInt(this);
mPrivateFlags2.setInt(this, currentValue | 0x00000001);
} catch (Exception e) {
}
I have the same problem. I did not solved this recycling problem, but I found a possible workaround still using the Drag & Drop framework. The idea is to change of perspective: instead of using a OnDragListener on each View in the list, it can be used on the ListView directly.
Then the idea is to find on top of which item the finger is while doing the Drag & Drop, and to write the related display code in the ListAdapter of the ListView. The trick is then to find on top of which item view we are, and where the drop is done.
In order to do that, I set as an id to each view created by the adapter its ListView position - with View.setId(), so I can find it later using a combination of ListView.pointToPosition() and ListView.findViewById().
As a drag listener example (which is, I remind you, applied on the ListView), it can be something like that:
// Initalize your ListView
private ListView _myListView = new ListView(getContext());
// Start drag when long click on a ListView item
_myListView.setOnItemLongClickListener(new AdapterView.OnItemLongClickListener() {
#Override
public boolean onItemLongClick(AdapterView<?> parent, View view, int position, long id) {
DragShadowBuilder shadowBuilder = new View.DragShadowBuilder(view);
view.startDrag(null, shadowBuilder, _myListView.getItemAtPosition(position), 0);
return true;
}
});
// Set the adapter and drag listener
_myListView.setOnDragListener(new MyListViewDragListener());
_myListView.setAdapter(new MyViewAdapter(getActivity()));
// Classes used above
private class MyViewAdapter extends ArrayAdapter<Object> {
public MyViewAdapter (Context context, List<TimedElement> objects) {
super(context, 0, objects);
}
#Override
public View getView(int position, View convertView, ViewGroup parent) {
View myView = convertView;
if (myView == null) {
// Instanciate your view
}
// Associates view and position in ListAdapter, needed for drag and drop
myView.setId(position);
return myView;
}
}
private class MyListViewDragListener implements View.OnDragListener {
#Override
public boolean onDrag(View v, DragEvent event) {
final int action = event.getAction();
switch(action) {
case DragEvent.ACTION_DRAG_STARTED:
return true;
case DragEvent.ACTION_DRAG_DROP:
// We drag the item on top of the one which is at itemPosition
int itemPosition = _myListView.pointToPosition((int)event.getX(), (int)event.getY());
// We can even get the view at itemPosition thanks to get/setid
View itemView = _myListView.findViewById(itemPosition );
/* If you try the same thing in ACTION_DRAG_LOCATION, itemView
* is sometimes null; if you need this view, just return if null.
* As the same event is then fired later, only process the event
* when itemView is not null.
* It can be more problematic in ACTION_DRAG_DROP but for now
* I never had itemView null in this event. */
// Handle the drop as you like
return true;
}
}
}
Now if you need to have a visual feedback when doing a drag and drop, there are several strategies. You can for instance have 2 instance variables in your activity named:
private boolean ongoingDrag = false; // To know if we are in a drag&drop state
private int dragPosition = 0; // You put the itemPosition variable here
When doing the drag and drop in MyListViewDragListener you modify these variables, and you use their state in MyViewAdapter. Of course do not forget to update the UI (in the event thread of course, use a Handler) with something like _myListView.getAdapter()).notifyDataSetChanged() or maybe _myListView.invalidate() method.
The problem is because listView.getPositionForView(view) returns -1 if the view is not visible when it is called. So relying on that will fail when you scroll the list. So, instead of setting a view.setOnLongClickListener() you can set a listView.setOnItemLongClickListener() on the list item which calls startDrag() on the item. onItemLongClick() gives you the position which you can pass to in the myLocalState parameter of startDrag(). Then you recover that in onDrag() using event.getLocalState() and casting it to an Integer. Like this...
listView.setOnItemLongClickListener(new AdapterView.OnItemLongClickListener() {
#Override
public boolean onItemLongClick(AdapterView<?> parent, View view, int position, long id) {
position -= listView.getHeaderViewsCount();
DragShadowBuilder dragShadow = new View.DragShadowBuilder(view);
view.startDrag(null, dragShadow, position, 0);
return true;
}
});
Then in your OnDragListener...
#Override
public boolean onDrag(View eventView, DragEvent event) {
Integer dragViewPos = ((Integer) event.getLocalState());
int eventViewPos = listView.getPositionForView(eventView) - listView.getHeaderViewsCount();
...
}

ViewPager and AsyncTask for loading the Items

I've a ViewPager with an PageAdapter loading ImageViews.
The images displayed come from the internet and I use an AsyncTask for loading the Bitmap and then (if the Page hasn't been destroyed) assign it to the ImageView.
#Override
public Object instantiateItem(ViewGroup container, int position) {
ImageView img = new ImageView(container.getContext());
new LoadImageTask(img,Data.getImageUrL(position)).execute();
container.addView(img );
return img;
}
So the ImageView has two states: loading or loaded. I need to track if the user waits for the image. So I implement this at the LoadImageTask onPostExecute:
if (loadListener != null) {
loadListener.onImageLoaded(position);
}
And in the listener:
public void onImageLoaded(int position) {
if (position == mViewPager.getCurrentItem()) {
// Save record ..
}
}
Since I only need to count the images displayed at the "Current Item of the ViewPager" this method is not enought because if the user moves to the item 5 for example, the ViewPager loads in cache the item 6. If the user waits enought time for this load (beeing the item 5 the current) the callback to onImageLoaded is lost (because the item 6 is not the currentItem at the moment, and then when the user moves to the item 6 I've no way of knowing if the image is loaded or not.
I was thinking in adding something like [[PSEUDOCODE]]:
mViewPager.setOnPageChangeListener(new OnPageChangeListener() {
#Override
public void onPageSelected(int position) {
if (mViewPager.isLoaded(posistion)){
// Save record ...
}
}
But I dont know how to retrieve if the corresponding view loaded the image or not at this moment. How can I do it?

Categories