Android Progress bar inside menu item is not updating properly - android

I have two sensor connected with my android app. Both apps are connecting over BLE and I get two types of data from these sensors.
Sensor Data
Power Value
To show the power status, I followed a tutorial to create the progressbar inside actionbar menu as follows.
theme.xml
<!-- Power Progress Theme -->
<style name="PowerProgressBar">
<item name="android:indeterminateOnly">false</item>
<item name="android:progressDrawable">#drawable/ic_action_power_progress</item>
</style>
layout_power_progress.xml
<?xml version="1.0" encoding="utf-8"?>
<FrameLayout xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:tools="http://schemas.android.com/tools"
android:layout_height="wrap_content"
android:layout_width="wrap_content"
style="#android:style/Widget.ActionButton">
<ProgressBar
android:id="#+id/powerProgress"
style="#style/PowerProgressBar"
android:layout_width="24dp"
android:layout_height="24dp"
android:visibility="visible"
android:layout_gravity="center" />
</FrameLayout>
menu/main.xml
<item
android:id="#+id/action_power_sensor_1"
android:title="#string/action_power_sensor_1"
app:actionLayout="#layout/layout_power_progress"
app:showAsAction="always" />
<item
android:id="#+id/action_power_sensor_2"
android:title="#string/action_power_sensor_2"
app:actionLayout="#layout/layout_power_progress"
app:showAsAction="always" />
override fun onPrepareOptionsMenu(menu: Menu?): Boolean {
/*---------- Sensor 1 ---------------*/
sensor1PowerProgressView = menu?.findItem(R.id.action_power_sensor_1)?.actionView?.findViewById(R.id.powerProgress)
sensor1PowerProgressView ?.max = 5
sensor1PowerProgressView ?.progress = sensor1Power
/*---------- Sensor 2 ---------------*/
sensor2PowerProgressView = menu?.findItem(R.id.action_power_sensor_2)?.actionView?.findViewById(R.id.powerProgress)
sensor2PowerProgressView ?.max = 100
sensor2PowerProgressView ?.progress = sensor2Power
return super.onPrepareOptionsMenu(menu)
}
/*--- Code where the power is getting updated ----*/
sensorViewModel.getSensor1Power().observe(this) {
sensor1Power = it
if (sensor1PowerPrev!=sensor1Power) {
sensor1PowerPrev = sensor1Power
invalidateOptionsMenu()
}
}
sensorViewMode2.getSensor2Power().observe(this) {
sensor1Power = it
if (sensor2PowerPrev!=sensor2Power) {
sensor2PowerPrev = sensor2Power
invalidateOptionsMenu()
}
}
I am updating these power values from sensorViewModel. I see the values inside the sensorViewModel getting updated properly upon sensor connect and disconnect. I am logging both values (sensor1Power & sensor2Power ) inside the onPrepareOptionsMenu and I see the values getting logged there properly.
Now after spending whole 2 weeks on this issue I can't figure out why sensor1PowerProgressView and sensor2PowerProgressView are not getting updated on disconnect. They retain the last value.
Anyone, can point out what am I doing incorrectly. if you need any more details, I can share upon request.

Related

Android ProgressBar does not work unless the progress value and max value are the same

The xml code:
<ProgressBar
android:id="#+id/meditationProgressBar"
style="?android:attr/progressBarStyleHorizontal"
android:layout_width="100dp"
android:layout_height="100dp"
android:layout_gravity="center"
android:background="#drawable/circular_progress_gray"
android:indeterminate="false"
android:max="3660"
android:progress="100"
android:progressDrawable="#drawable/circular_progress_bar"
android:rotation="-90" />
I set android:max="3660" because that's the maximum value the progressbar will ever have.
Kotlin Code:
override fun onTick(millisUntilFinished: Long) {
meditationProgress++
meditationProgressBar.progress=meditationTimeInSeconds-meditationProgress
}
Variables:
private var meditationProgress = 0
private var meditationTimeInSeconds:Int = meditationTime*60
I actually tried replacing "meditationTimeInSeconds" by "30" in the onTick function and I changed the android:max of the ProgressBar to 30 and the progress bar actually worked but I have no idea why it isn't working now.
Edit 1:
private var meditationTimeInMilliSeconds = meditationTime*60000
meditateTimer=object: CountDownTimer(meditationTimeInMilliSeconds.toLong(),1000){
override fun onTick(millisUntilFinished: Long) {
meditationProgress++
meditationProgressBar.progress=meditationTimeInSeconds-meditationProgress
meditationTvTimer.text=(meditationTimeInSeconds-meditationProgress).toString()
}
I actually tried not using the drawable i am using for the progressbar and it still doesn't work. Anyways, here's the code for it.
<?xml version="1.0" encoding="utf-8"?>
<layer-list xmlns:android="http://schemas.android.com/apk/res/android">
<item>
<shape android:shape="ring"
android:innerRadiusRatio="2.7"
android:thicknessRatio="50"
android:useLevel="true">
<solid android:color="#color/black"/>
</shape>
</item>
</layer-list>
It's still the same. The progressbar doesn't work unless the progress value and the android:max value of the progressbar are the same
I think this is probably because with a max of 3660, your range is so large that small changes aren't really noticeable, especially if your display is only 100dp x 100dp.
Assuming 3660 is seconds... actually let's call it 3600 to make the maths easier, it would take 10 seconds to move 1 degree around a circular progress bar, or 36 seconds to change a 100dp-tall one by 1 dp. You might be able to see the bar/gap appear when it first starts from empty/full, but it's going to be hard to see it moving.
Whereas if it's just a 30 second range, it'll move pretty quick and be obvious that it's working. I tested your code (without the custom drawable which I don't have, I just let it go vertical) and it works fine - it just takes a while to see progress when the full range is over an hour
edit: if it helps, here's my code that works:
class MainFragment : Fragment(R.layout.fragment_main) {
private val meditationTime = 31
private var meditationProgress = 0
private var meditationTimeInSeconds: Int = meditationTime * 60
private lateinit var meditationProgressBar: ProgressBar
override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
super.onViewCreated(view, savedInstanceState)
meditationProgressBar = view.findViewById(R.id.meditationProgressBar)
object : CountDownTimer(meditationTimeInSeconds * 1000L, 1000L) {
override fun onTick(millisUntilFinished: Long) {
meditationProgress++
meditationProgressBar.progress = meditationTimeInSeconds - meditationProgress
Log.d("TICK", "Progress: ${meditationProgress.toFloat() / meditationTimeInSeconds}")
}
override fun onFinish() {
TODO("Not yet implemented")
}
}.start()
}
}
<?xml version="1.0" encoding="utf-8"?>
<FrameLayout
xmlns:android="http://schemas.android.com/apk/res/android"
android:layout_width="match_parent"
android:layout_height="match_parent">
<ProgressBar
android:id="#+id/meditationProgressBar"
style="?android:attr/progressBarStyleHorizontal"
android:layout_width="300dp"
android:layout_height="300dp"
android:layout_gravity="center"
android:indeterminate="false"
android:progressDrawable="#drawable/circular_progress_bar"
android:max="3660"
android:progress="100"
android:rotation="-90" />
</FrameLayout>
I copied your code and changed meditateTimer=object: CountDownTimer(meditationTimeInMilliSeconds.toLong(),1000) to meditateTimer=object: CountDownTimer(meditationTimeInMilliSeconds.toLong(),200) mean reduced countdown timer interval to see whether progress bar value is changing or not. Progress started from 1858 and decreasing by one after 200ms. After few seconds I saw that the progress bar is changing accordingly. So there is nothing wrong with progress bar the only thing is that the max progress value is large and there is very little change occur after 1000 ms delay in your code which is not easily detected by human eyes. You may wait for a long time to detect the changes.

How to use notifyDataSetChanged with the new NavigationView?

I use the new NavigationView in one of my recent projects. However I have a problem for the update data.
Previously, I used a ListView in my DrawerLayout and when I needed to change my data I called notifyDataSetChanged() method of my Adapter.
Currently NavigationView does not notifyDataSetChanged() method and when I want to update an item on my menu nothing is happening, for example:
Menu menuAccount = navigationView.getMenu().findItem(R.id.drawer_item_account).getSubMenu();
menuAccount.findItem(R.id.drawer_item_login).setVisible(!isLoggedIn);
Do you have a solution ? Thanks you for your help.
UPDATE: This was fixed in v23.0.0 so you don't need to do anything by youself, just update your dependency. Got from this answer: https://stackoverflow.com/a/32181083/2489474
Old solution (just to see how stupid it could be):
But I've found working solution in this answer https://stackoverflow.com/a/30604299/2489474. It uses reflection to call updateMenuView(boolean) of NavigationView's presenter.
I've modified code from the answer for my purposes. Check also a method from the answer and choose which one is better for you.
//HACK
private void updateNavigationView() {
try {
Field presenterField = NavigationView.class.getDeclaredField("mPresenter");
presenterField.setAccessible(true);
((NavigationMenuPresenter) presenterField.get(navigationView_)).updateMenuView(false);
} catch (NoSuchFieldException e) {
e.printStackTrace();
} catch (IllegalAccessException e) {
e.printStackTrace();
}
}
P.S. it is interesting that updateMenuView ignores a value was given to it.
#Moinkhan Thanks you for your helper but doesn't work for me. Here my menu_drawer.xml
<group
android:checkableBehavior="single"
android:id="#+id/group1">
<item
android:id="#+id/drawer_item_publications_list"
android:icon="#drawable/ic_drawer_publications_24dp"
android:title="#string/drawer_menu_item_publications" />
</group>
<group android:id="#+id/drawer_group_account">
<item
android:title="#string/drawer_menu_sub_item_account"
android:id="#+id/drawer_item_account">
<menu>
<item
android:icon="#drawable/ic_drawer_login_24dp"
android:id="#+id/drawer_item_login"
android:title="#string/drawer_menu_item_login" />
<group
android:id="#+id/group_actions_user">
<item
android:icon="#drawable/ic_drawer_add_publication_24dp"
android:id="#+id/drawer_item_add_publication"
android:title="#string/drawer_menu_item_add_publication" />
<item
android:icon="#drawable/ic_drawer_my_publications_24dp"
android:id="#+id/drawer_item_my_publications"
android:title="#string/drawer_menu_item_my_publications" />
<item
android:icon="#drawable/ic_drawer_edit_profil_24dp"
android:id="#+id/drawer_item_edit_profil"
android:title="#string/drawer_menu_item_edit_profil" />
<item
android:icon="#drawable/ic_drawer_delete_account_24dp"
android:id="#+id/drawer_item_delete_account"
android:title="#string/drawer_menu_item_delete_account" />
<item
android:icon="#drawable/ic_drawer_logout_24dp"
android:id="#+id/drawer_item_logout"
android:title="#string/drawer_menu_item_logout" />
</group>
</menu>
</item>
</group>
And my method to update my NavigationView
private void setUpNavigationDrawer()
{
boolean isLoggedIn = sessionManager.isLoggedIn();
navigationView.getMenu().findItem(R.id.drawer_item_account).getSubMenu().findItem(R.id.drawer_item_login).setVisible(!isLoggedIn);
navigationView.getMenu().findItem(R.id.drawer_item_account).getSubMenu().setGroupVisible(R.id.group_actions_user, isLoggedIn);
}
After some operations I called setUpNavigationDrawer() but the menu was not updated !
Instead of referencing group and then finding a item from that group and setting it's visibility try to reference the item directly like this ...
navView.getMenu().findItem(R.id.drawer_item_login).setVisible(!isLoggedin);
navView.getMenu().findItem(R.id.drawer_item_account).getSubMenu().setGroupVisible(R.id.group_actions_user, !isLoggedin);
It works for me. I hope it helps you.

Android RatingBar painting itself wrong

I need to force a repaint of the RatingBar control.
After many problems with the ratingbar and styles, I managed to get almost everything working.
I use it in a listview item. Due to how it works, one has to "fight" a little with its look and behavior. I ended up using a soluion I found on SO where one sets it to work as an indicator, but where oneself manually calculates what score the click on the rating bar corresponds to. The code always yield the correct result when steeping through the code, but the control painting of itself is wrong the first time. Here's my code in getView "part one":
final RatingBar rating = (RatingBar)view.findViewById(R.id.listitem_catalog_rating);
rating.setOnTouchListener(new OnTouchListener() {
#Override
public boolean onTouch(View v, MotionEvent event) {
if (event.getAction() == MotionEvent.ACTION_UP) {
float touchPositionX = event.getX();
float width = rating.getWidth();
float starsf = (touchPositionX / width);
starsf = starsf * param_final__data.score_max; // 5
int starsint = (int) starsf + param_final__data.score_min;
byte starsbyte = (byte) starsint;
param_final__data.score_cur = starsbyte;
starsf = starsint;
rating.setRating(starsf);
rating.setVisibility(View.INVISIBLE);
// force repaint and set visible - how?
}
else
if (event.getAction() == MotionEvent.ACTION_DOWN) {
param_final__view.setPressed(true);
}
else
if (event.getAction() == MotionEvent.ACTION_CANCEL) {
param_final__view.setPressed(false);
}
return true;
}
});
The problem is.When the ratingbar is initially shown, the first time when clicking anywhere on it will make it draw itself as if one always chosen the max score. However, if one hides the control and shows it again - it draws everything correctly. However, this is with "natural user interaction delay" - e.g. by clicking a button that switches the visibility state. If I try to force a repaint in code using invalidate or setvisibility instructions, nothing happens.
This is the code "part 2" where I initialize the ratingbar in getView when it is shown:
rating.setIsIndicator(true);
rating.setVisibility(View.VISIBLE);
rating.setStepSize(1);
//rating.setMax(data.score_max);
rating.setNumStars(data.score_max);
rating.setRating(data.score_cur);
And this is its XML:
<RatingBar
android:id="#+id/listitem_catalog_rating"
android:layout_height="wrap_content"
android:layout_width="wrap_content"
android:numStars="1"
android:stepSize="1.0"
android:rating="1.0"
style="#style/MicRatingBar"
android:visibility="gone"
android:layout_marginTop="10dip"
android:layout_marginBottom="10dip"
/>
...
<style name="MicRatingBar" parent="#android:style/Widget.RatingBar">
<item name="android:progressDrawable">#drawable/micratingstars</item>
<item name="android:minHeight">34dip</item>
<item name="android:maxHeight">34dip</item>
</style>
...
<?xml version="1.0" encoding="utf-8"?> <layer-list xmlns:android="http://schemas.android.com/apk/res/android">
<item android:id="#+android:id/background" android:drawable="#drawable/star_background" />
<item android:id="#+android:id/secondaryProgress" android:drawable="#drawable/star_secondaryprogress" />
<item android:id="#+android:id/progress" android:drawable="#drawable/star_progress" /> </layer-list>
For reference, these are some of the stackoverflows which got me as far as I am:
Capture RatingBar Click
Android RatingBar as Button when isIndicator = True
How to make a smaller RatingBar?
(but unfortunately does not solve my specific problem.)
I have tried lots of different combinations, and in my case, the code posted here is what got closest to the desired behavior and look. Just with the problem the score is drawn incorrectly on "first show".
I have tried to use e.g. invalidate, but I believe its internal flags makes it ignore he invalidate the request.
I think the problem lies in the order of these statements:
// Fine
rating.setIsIndicator(true);
// Fine
rating.setVisibility(View.VISIBLE);
// Problem - Although not documented, I think this should be
// called after `setNumStars(int)`
rating.setStepSize(1);
// Switch with the statement above
rating.setNumStars(data.score_max);
// Fine
rating.setRating(data.score_cur);
So, try this order:
rating.setIsIndicator(true);
rating.setVisibility(View.VISIBLE);
rating.setNumStars(data.score_max);
rating.setStepSize(1);
rating.setRating(data.score_cur);
As for // force repaint and set visible - how?, this shouldn't be required. setRating(float) should force an update. Just remove rating.setVisibility(View.INVISIBLE); from ACTION_UP part of your code. If the RatingBar still does not update, try rating.requestLayout(). But, please read further for a cleaner solution.
You said that int starsint = (int) starsf + param_final__data.score_min; is getting the correct value. And I am guessing that param_final__data.score_cur = starsbyte; updates your ListView data. Then why not just call notifyDataSetChanged(), and let the adapter update the view with the correct value? The change in the order of statements will be required though.

Animation-list on Menu Item Icon

I want to have an Menu item in the ActionBar that visualizes a boolean value. If this boolean value is true, the icon should start animating a series of 2 images in a loop. I did the following:
I added an item in the menu.xml file:
<item
android:id='#+id/myAnimation'
android:icon='#drawable/pic1'
android:showAsAction='ifRoom'>
</item>
I created an animation-list:
<?xml version='1.0' encoding='utf-8'?>
<animation-list xmlns:android='http://schemas.android.com/apk/res/android'
android:oneshot='false' >
<item
android:drawable='drawable/pic1'
android:duration='100' />
<item
android:drawable='drawable/pic2'
android:duration='100' />
</animation-list>
In the method onCreateOptionsMenu I assign this menu item to a variable
myMenuItem = menu.findItem(R.id.myAnimation);
Now, I was hoping to add an animation which would get executed whenever I tell the menu item to animate. But I wasn't able to come even close.
is this even possible?
How can I do that?
Thanks! Any help is appreciated!
Edit:
The App should support API from 8
I did ((AnimationDrawable) item.getIcon()).start(); in onCreateOptionsMenu(Menu menu)
This did not work. Then I thought, maybe it should be done on the UI thread, and then this worked:
someView.postDelayed(new Runnable() {
#Override
public void run() {
((AnimationDrawable) item.getIcon()).start();
}
}, 1000);
I was looking for the same thing and just found your question unanswered, so here is the solution, from the Android dev website:
((AnimationDrawable)myMenuItem).start();

Single inflation of a style to be used multiple times

I want to inflate an item once and use it in a loop. I currently have a solution, but there is most likely a better way. Also, the program won't run unless there is view.removeView call, which makes sense, but seems hazardous if I ever want to add catBtn later in the app).
Existing code:
LinearLayout col1 = (LinearLayout)findViewById(R.id.col1);
for(int i = 0; i < 10; ++i) {
LinearLayout assets = (LinearLayout)this.getLayoutInflater().inflate(R.layout.assets, null);
Button btn = (Button)assets.findViewById(R.id.catBtn);//new Button(this);
assets.removeView(btn);
col1.addView(btn);
}
Existing layout.assets
<?xml version="1.0" encoding="utf-8"?>
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:weightSum="1"
android:id="#+id/assets">
<ImageView android:focusable="true"
android:id="#+id/thumb"
android:background="#drawable/selectable"
android:layout_marginBottom="20dip"
android:src="#drawable/icon" android:layout_height="140dip" android:layout_width="250dip"/>
<Button android:id="#+id/catBtn"
android:layout_height="wrap_content"
android:background="#drawable/selectable"
android:text="Cat Button"
android:layout_width="120dip"
android:textSize="16dip"></Button>
</LinearLayout>
You could pass false as the last parameter to the inflate method
LayoutInflator.from(context).inflate(res, parent, false);
Which causes the inflated view to be attached to nothing. That way you don't have to remove anything. That gets rid of the assets.removeView() issue. But I think this still might be wasteful.
It looks like you just want some buttons:
<Button android:id="#+id/catBtn"
android:layout_height="wrap_content"
android:background="#drawable/selectable"
android:text="Cat Button"
android:layout_width="120dip"
android:textSize="16dip">
Let's extract that to a style:
<resources>
<declare-stylable android:name="awesome_button">
<attr android:name="awesomeButtonStyle" android:type="reference"/>
</declare-stylable>
<style android:name="AwesomeButton">
<item android:name="android:layout_height">wrap_content</item>
<item android:name="android:background">#drawable/selectable</item>
<item android:name="android:layout_width">120dp</item>
<item android:name="android:text">Cat Button</item>
<item android:name="android:textSize">16sp</item>
</style>
<style android:name="Theme.WithAwesomeButtons" parent="#android:style/Theme">
<item android:name="awesomeButtonStyle">#style/AwesomeButton</item>
</style>
<resources>
OK now we are rolling with style ;) (sorry couldn't resist). Now let's configure your Activity, inside the AndroidManifest.xml:
<activity android:name=".MyCatBtnActivity"
... Whatever else is in your activity
android:theme="#style/Theme.WithAwesomeButtons"/>
OK now within your loop:
for (int i=0; i<10; i++) {
// Let's get rid of the LayoutInflator (unless you want to use an xml layout
// in which case, make awesomeButton.xml and have it just have a button in it
// with attribute style="?awesomeButtonStyle").
Button button = new Button(this, null, R.attr.awesome_button.awesomeButtonStyle));
// Let's tag them with the integer counter so we can id them later
// You can set id, but there is a slight chance it will not be unique
// within the hierarchy. Later on you can either use col1.getChildView(index) to scan
// and look for these tags (or store them in a local array if col1 holds a lot of views)
// Then you can also evaluate the tag whenever you are referring to a button from
// within an OnClickListener or any View listener for that matter.
button.setTag(Integer.valueOf(i));
col1.add(button);
}
I think this is sort of what you are trying to achieve.

Categories

Resources