Whatever I do, I only get small circular indeterminate progress bar. What I need is horizontal determinate. I tried setIndeterminate(false) explicitly in the code, setting max and initial progress, pre-setting it visible, setting the style explicitly to #android:style/Widget.ProgressBar.Horizontal or style/Widget.AppCompat.ProgressBar.Horizontal - nothing helped, the scrollbar remained indeterminate circle. While in the Studio design preview it shows as horizontal progress all right (when made visible).
Any idea please on where I could be wrong?
The Android version is 5.0.
Layout excerpt:
<ProgressBar
android:id="#+id/loadingProgress"
style="?android:attr/progressBarStyleHorizontal"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_alignParentStart="true"
android:layout_alignParentTop="true"
android:indeterminate="false"
android:visibility="gone"
/>
Code snippets (space lines and irrelevant code removed):
ProgressBar pbLoading;
public void onCreate( Bundle _savedInstanceState ) {
/* ... */
pbLoading = findViewById( R.id.loadingProgress );
/* ... */
}
public void onStartLoading() {
pbLoading.setVisibility( ProgressBar.VISIBLE );
pbLoading.setMax( 100 );
pbLoading.setProgress( 0 );
}
public void onLoadProgress( double _progress ) {
pbLoading.setProgress( (int)Math.round( _progress * 100 ) );
}
public void onDataLoaded() {
fDataLoaded = true;
pbLoading.setVisibility( View.GONE );
}
The problem description was not complete. This app uses the old-fashioned approach with placing most of its logic in library project and overriding individual aspects in application projects. So, it turned out that the layout was overridden by an almost-copy one with the same layout id in the application project.
The problem is solved now.
Related
I have a Spinner in my app, with customized dropdown views. This is what the layout of the dropdown items look like:
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:app="http://schemas.android.com/apk/res-auto"
xmlns:tools="http://schemas.android.com/tools"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:focusable="false"
android:orientation="horizontal">
<androidx.appcompat.widget.AppCompatImageButton
android:id="#+id/leadingButton"
android:layout_width="48dp"
android:layout_height="48dp"
android:layout_weight="0" />
<FrameLayout
android:layout_width="0dp"
android:layout_height="48dp"
android:layout_gravity="center_vertical"
android:layout_weight="1">
<RelativeLayout
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:orientation="vertical">
<TextView
android:id="#+id/dropdown_text"
android:layout_width="match_parent"
android:layout_height="wrap_content" />
<androidx.appcompat.widget.AppCompatTextView
android:id="#+id/dropdown_text_subtitle"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_below="#id/dropdown_text" />
</RelativeLayout>
</FrameLayout>
<androidx.appcompat.widget.AppCompatImageButton
android:id="#+id/trailingButton"
android:layout_width="48dp"
android:layout_height="48dp"
android:layout_weight="0" />
</LinearLayout>
Android Studio warns me that my FrameLayout is useless. But when I take out the FrameLayout the dropdown views become narrow, and don't align with the spinner itself anymore. I have had the same problem when I tried to rewrite the drop-down items with a ConstraintLayout: the dropdown list became narrow, about half of the Spinner's size, and could not display all text, even though the ConstraintLayout had android:layout_width="match_parent".
A sketch to illustrate what I mean:
Why does this happen? How can I predict what the width of the dropdown menu will be based on the layout?
I find this dropdown view sizing quite magical
Did you look at the source code of the Spinner class? I just did. Here's what I found (API 27 Sources):
The spinner uses a ListView internally (first LOL), backed by DropdownPopup (private class):
private class DropdownPopup extends ListPopupWindow implements SpinnerPopup {
Before looking at it, look at ListPopupWindow because has a lot of info about the problems it has to deal with. It's a big class but among these things, you can see:
private int mDropDownHeight = ViewGroup.LayoutParams.WRAP_CONTENT;
private int mDropDownWidth = ViewGroup.LayoutParams.WRAP_CONTENT;
private int mDropDownHorizontalOffset;
private int mDropDownVerticalOffset;
It appears the DropDown is -by default- WRAPPING the content based upon the base class, however, the DropDownPopup that drives (and contains the adapter with all the items in the spinner) also has a void computeContentWidth() { method.
This method is called from the show() method, so before showing the popup, this computation happens every time.
I think here's part of the answer you're looking for:
void computeContentWidth() {
final Drawable background = getBackground();
int hOffset = 0;
if (background != null) {
background.getPadding(mTempRect);
hOffset = isLayoutRtl() ? mTempRect.right : -mTempRect.left;
} else {
mTempRect.left = mTempRect.right = 0;
}
final int spinnerPaddingLeft = Spinner.this.getPaddingLeft();
final int spinnerPaddingRight = Spinner.this.getPaddingRight();
final int spinnerWidth = Spinner.this.getWidth();
if (mDropDownWidth == WRAP_CONTENT) {
int contentWidth = measureContentWidth(
(SpinnerAdapter) mAdapter, getBackground());
final int contentWidthLimit = mContext.getResources()
.getDisplayMetrics().widthPixels - mTempRect.left - mTempRect.right;
if (contentWidth > contentWidthLimit) {
contentWidth = contentWidthLimit;
}
setContentWidth(Math.max(
contentWidth, spinnerWidth - spinnerPaddingLeft - spinnerPaddingRight));
} else if (mDropDownWidth == MATCH_PARENT) {
setContentWidth(spinnerWidth - spinnerPaddingLeft - spinnerPaddingRight);
} else {
setContentWidth(mDropDownWidth);
}
if (isLayoutRtl()) {
hOffset += spinnerWidth - spinnerPaddingRight - getWidth();
} else {
hOffset += spinnerPaddingLeft;
}
setHorizontalOffset(hOffset);
}
You may want to DEBUG and set breakpoints here to observe what these values are and what they mean.
The other piece there is the setContentWidth() method. This method is from the ListPopupWindow, and looks like:
/**
* Sets the width of the popup window by the size of its content. The final width may be
* larger to accommodate styled window dressing.
*
* #param width Desired width of content in pixels.
*/
public void setContentWidth(int width) {
Drawable popupBackground = mPopup.getBackground();
if (popupBackground != null) {
popupBackground.getPadding(mTempRect);
mDropDownWidth = mTempRect.left + mTempRect.right + width;
} else {
setWidth(width);
}
}
And setWidth (also in that class) all it does is:
/**
* Sets the width of the popup window in pixels. Can also be {#link #MATCH_PARENT}
* or {#link #WRAP_CONTENT}.
*
* #param width Width of the popup window.
*/
public void setWidth(int width) {
mDropDownWidth = width;
}
This mDropDownWidth seems used all over the place, but also made me found this other method in ListPopupWindow...
/**
* Sets the width of the popup window by the size of its content. The final width may be
* larger to accommodate styled window dressing.
*
* #param width Desired width of content in pixels.
*/
public void setContentWidth(int width) {
Drawable popupBackground = mPopup.getBackground();
if (popupBackground != null) {
popupBackground.getPadding(mTempRect);
mDropDownWidth = mTempRect.left + mTempRect.right + width;
} else {
setWidth(width);
}
}
So there you have it, more logic needed including the "window dressing" (?)
I agree the Spinner is a badly designed class (or rather, with outdated design) and even more so with the name (at this Google I/O in 2019, they actually explained in one of the sessions why the name "Spinner" hint: it comes from the 1st android prototypes). By looking at all this code, it would take a few hours to figure out what the spinner is trying to do and how it works, but the trip won't be pleasant.
Good luck.
I will reiterate my advice to use ConstraintLayout which you said you were familiar with; at the very least, discard weights.
By looking at how this works (ListView!!!) the weight calculation warrants a 2nd measure/layout pass, which is not only extremely inefficient and not needed, but also may be causing issues with the internal data adapter this DropDown thing manages so the "list" is displayed.
Ultimately, another class is also involved, this is all presented in a PopupView. PopupViews are what you see when you open a Menu item for example, and are very hard to customize sometimes, depending what you want to do.
Why Google chose this approach at the time, I don't know, but it certainly warrants an update and Material Design hasn't brought much to the table in this regard yet, as it will always be incomplete or in alpha state a year behind anything else.
It is telling you the FrameLayout is useless because it has a single child view ( the Relative Layout).
Your Framelayout has width defined as so:
<FrameLayout
android:layout_width="0dp"
android:layout_weight="1"
Your relative layout has its width defined as:
<RelativeLayout
android:layout_width="match_parent"
So just removing the FrameLayout means that a different "rule" is in place for the width.
To truly replace the FrameLayout with the RelativeLayout it should look like this:
<RelativeLayout
android:layout_width="0dp"
android:layout_weight="1"
I'm working on an Android TV app and need to make a progress bar to indicate how much content has been played already. I feel like what I've written should be working, but it isn't.
I've got a LinearLayout with android:orientation="horizontal" and android:weightSum=100 as a container for the progress bar, and the actual progress bar is a FrameLayout inside the container, with android:layout_width="0dp" and android:layout_weight="0" to make sure the progress bar isn't drawn if there isn't any progress.
In the activity code I'm attempting to change the weight of the progress bar like this:
ViewGroup.LayoutParams lp = progressView.getLayoutParams();
LinearLayout.LayoutParams params = new LinearLayout.LayoutParams(lp.width, lp.height, progress);
progressView.setLayoutParams(params);
(progressView, naturally, is the progress bar object I'm trying to manipulate, and progress is the intended new weight)
I was initially running this code in the activity's onCreate() method, until I remembered you can't do anything about a view's dimensions until it has been laid out properly. Since then I've tried running this in onResume() with postDelayed like this:
#Override
public void onResume() {
super.onResume();
progressView.postDelayed(new Runnable() {
#Override
public void run() {
setProgress();
}
}, 1000);
}
and with a ViewTreeObserver like this during onCreate():
progressView.getViewTreeObserver().addOnGlobalLayoutListener(new ViewTreeObserver.OnGlobalLayoutListener() {
#Override
public void onGlobalLayout() {
if (!progressBarRefreshed) {
setProgress();
}
}
});
and I've even tried running the code as an onClick event attached to a button in the same activity (in both cases setProgress() is the function that performs the weight change using the code posted above). I've verified (with log messages I removed from the code to make it more readable) that the initial width is 0, height is -1 (match_parent) and weight is 0, and that the new LayoutParams object has width 0, height -1 and weight equal to whatever I set it to. But after the setLayoutParams() call the view still has weight 0 and there is no change on the screen either. What blatantly trivial thing am I missing here?
Try calling progressView.requestLayout() after setting the layout params.
Question Summary: How can I make a ProgressBar integrated inside the ActionBar, like on the Chrome App?
Details: Look at this screenshot from Chrome:
I want to create an Action Bar just like this. Just under the Action Bar, there's a ProgressBar that fills according to page load. I've seen this example from many apps, like Feedly, but I haven't been able to create my own implementation. I tried using Android's own APIs to create it:
#Override
protected void onCreate(Bundle savedInstanceState) {
//Request Permission to display the Progress Bar...
this.requestWindowFeature(Window.FEATURE_PROGRESS);
this.setWindowContentView(R.layout.activity_main)
super.onCreate(savedInstanceState);
this.setProgressBarIndeterminate(true);
}
But this code only causes the ProgressBar to show over the Action Bar, like so:
So, how can I make my ProgressBar appear under the Action Bar, like on the Chrome App?
I wasn't fully satisfied with the accepted answer above, so I did some extra research myself.
The trick I believe they used is that they retrieved the top view in the view hierarchy called DecorView and added the progress bar in there. That way, the progress bar displays both over the action bar AND the content area. Note that the S.D.'s answer puts the progress bar into content area and 'steals' space from actual content, what can lead to unexpected results.
Sample screenshots of this implementation:
Code
Just put this code into onCreate method of some activity and it should work:
// create new ProgressBar and style it
final ProgressBar progressBar = new ProgressBar(this, null, android.R.attr.progressBarStyleHorizontal);
progressBar.setLayoutParams(new LayoutParams(LayoutParams.MATCH_PARENT, 24));
progressBar.setProgress(65);
// retrieve the top view of our application
final FrameLayout decorView = (FrameLayout) getWindow().getDecorView();
decorView.addView(progressBar);
// Here we try to position the ProgressBar to the correct position by looking
// at the position where content area starts. But during creating time, sizes
// of the components are not set yet, so we have to wait until the components
// has been laid out
// Also note that doing progressBar.setY(136) will not work, because of different
// screen densities and different sizes of actionBar
ViewTreeObserver observer = progressBar.getViewTreeObserver();
observer.addOnGlobalLayoutListener(new OnGlobalLayoutListener() {
#Override
public void onGlobalLayout() {
View contentView = decorView.findViewById(android.R.id.content);
progressBar.setY(contentView.getY() - 10);
ViewTreeObserver observer = progressBar.getViewTreeObserver();
observer.removeOnGlobalLayoutListener(this);
}
});
You can play with LayoutParams' height argument to set the progressBar wider or narrower, but you might have to adjust the -10 offset.
Styling
Unfortunately, you can see the ugly gray background of the progress bar. To remove it, simply searching for the background by id and trying to hide it doesn't work. To remove the background, I had to create identical drawble of the system version and remove the background item.
TL;DR: Create file progress_horizontal_holo_no_background_light.xml and paste this drawable:
<?xml version="1.0" encoding="utf-8"?>
<layer-list xmlns:android="http://schemas.android.com/apk/res/android">
<item android:id="#android:id/secondaryProgress">
<scale android:scaleWidth="100%"
android:drawable="#drawable/progress_secondary_holo_light" />
</item>
<item android:id="#android:id/progress">
<scale android:scaleWidth="100%"
android:drawable="#drawable/progress_primary_holo_light" />
</item>
</layer-list>
Copy appropriate .png drawables from sdk/platforms/android-xx/data/res/drawable-xxx/ to your project and then in the code you can add:
progressBar.setProgressDrawable(getResources().getDrawable(R.drawable.progress_horizontal_holo_no_background_light));
Extra: Indeterminate Progress Bar
Pre-KitKat versions of indeterminate progress bars are pretty ugly and laggy.
You can download new smooth progressBar called ButteryProgressBar. Just search for it on google (I cannot post any more links, because I am new here :[ ), add the class into your project and you can simply replace the previous ProgressBar with this code and have crispy indeterminate progress bar:
final ButteryProgressBar progressBar = new ButteryProgressBar(this);
progressBar.setLayoutParams(new LayoutParams(LayoutParams.MATCH_PARENT, 24));
You might also need to simplify this code:
final TypedArray ta = c.obtainStyledAttributes(attrs, R.styleable.ButteryProgressBar);
try {
mBarColor = ta.getColor(R.styleable.ButteryProgressBar_barColor,
c.getResources().getColor(android.R.color.holo_blue_light));
mSolidBarHeight = ta.getDimensionPixelSize(
R.styleable.ButteryProgressBar_barHeight,
Math.round(DEFAULT_BAR_HEIGHT_DP * mDensity));
mSolidBarDetentWidth = ta.getDimensionPixelSize(
R.styleable.ButteryProgressBar_detentWidth,
Math.round(DEFAULT_DETENT_WIDTH_DP * mDensity));
} finally {
ta.recycle();
}
to this code:
mBarColor = c.getResources().getColor(android.R.color.holo_blue_light);
mSolidBarHeight = Math.round(DEFAULT_BAR_HEIGHT_DP * mDensity);
mSolidBarDetentWidth = Math.round(DEFAULT_DETENT_WIDTH_DP * mDensity);
Hope I helped :)
Extend Activities from following class to have a ProgressBar at the top(below ActionBar) of each, and a getProgressBar() method:
Parent class:
public abstract class ProgressActivity extends Activity {
private ProgressBar mProgressBar;
#Override
public void setContentView(View view) {
init().addView(view);
}
#Override
public void setContentView(int layoutResID) {
getLayoutInflater().inflate(layoutResID,init(),true);
}
#Override
public void setContentView(View view, ViewGroup.LayoutParams params) {
init().addView(view,params);
}
private ViewGroup init(){
super.setContentView(R.layout.progress);
mProgressBar = (ProgressBar) findViewById(R.id.activity_bar);
return (ViewGroup) findViewById(R.id.activity_frame);
}
protected ProgressBar getProgressBar(){
return mProgressBar;
}
}
Layout (progress.xml):
<?xml version="1.0" encoding="utf-8"?>
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android" android:orientation="vertical"
android:layout_width="match_parent" android:layout_height="match_parent">
<ProgressBar android:id="#+id/activity_bar"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_marginTop="-8dp"
style="#android:style/Widget.DeviceDefault.ProgressBar.Horizontal"
/>
<FrameLayout android:id="#+id/activity_frame"
android:layout_width="match_parent"
android:layout_height="fill_parent"
/>
</LinearLayout>
This is now a native behavior that can be obtained using SwipeRefreshLayout.
You can wrap your scrollable view with a SwipeRefreshLayout and then you just need to listen to onRefresh events:
#Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
swipeLayout = (SwipeRefreshLayout) findViewById(R.id.swipe_container);
swipeLayout.setOnRefreshListener(this);
swipeLayout.setColorScheme(android.R.color.holo_blue_bright,
android.R.color.holo_green_light,
android.R.color.holo_orange_light,
android.R.color.holo_red_light);
}
#Override public void onRefresh() {
new Handler().postDelayed(new Runnable() {
#Override public void run() {
swipeLayout.setRefreshing(false);
}
}, 5000);
}
A nice and simple tutorial can be found in this blog.
I have compiled code from this thread as well as other threads on StackOverflow and created a project that can be used to implement ButteryProgessbar as well as "pull down to refresh". https://github.com/pauldmps/Gmail-like-Pull-Down-to-Refresh
Feel free to use the code in your application.
A big thanks to you guys.
Please use below code. just use one default style in progress bar as style="?android:attr/progressBarStyleHorizontal"
<RelativeLayout xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:tools="http://schemas.android.com/tools"
android:layout_width="match_parent"
android:layout_height="match_parent"
>
<TextView
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="#string/hello_world" />
<ProgressBar
android:layout_width="match_parent"
android:layout_height="wrap_content"
style="?android:attr/progressBarStyleHorizontal"
android:indeterminate="true"
/>
</RelativeLayout>
Well , I did something similar for one of my pet apps and am not sure if that's the only way or the best way to do it , but it definitely works.
To begin with , use an overlay action bar and then set the background drawable to "null" so that the overlay action bar is transparent.
Now in you activity layout , set the top margin for your root view to "actionbar height" You can do it like this.
<RelativeLayout
...
android:layout_marginTop="?android:attr/actionBarSize" />
Now the action is not going to hide any of your activity content.
The next thing you have to do now is - add a header view on top of your root view with height same as the action bar. Set a background color to it. This would now be the color of your action bar and since the action bar aligns perfectly on top pf this header view.
Now you can easily put a progress bar in the header view and align it to the bottom of the header view. To the user this would look as if the progress bar is on the action bar itself , just like chrome ..
I've run into what I can only categorize as a memory leak for ScrollView elements when using the Gallery component.
A short background. I've got an existing app that is a photo slideshow app.
It uses the Gallery component, but each element in the adapter is displayed in full-screen.
(full source is available at this link)
The adapter View element consist of an ImageView, and two TextViews for title and description.
As the photos are of a quite high-resolution, the app uses quite a lot of memory but the Gallery has in general manage to recycle them well.
However, when I am now implementing a ScrollView for the description TextView, I almost immediately run into memory problems. This the only change I made
<ScrollView
android:id="#+id/description_scroller"
android:layout_width="fill_parent"
android:layout_height="wrap_content"
android:scrollbars="vertical"
android:fillViewport="true">
<TextView
android:id="#+id/slideshow_description"
android:textSize="#dimen/description_font_size"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:textColor="#color/white"
android:layout_below="#id/slideshow_title"
android:singleLine="false"
android:maxLines="4"/>
</ScrollView>
I did a heap dump and could clearly see that it was the Scrollview which was the root of the memory problems.
Here are two screenshots from the heap dump analysis. Note that the ScrollView retains a reference to mParent which includes the large photo I use
PS same problem occurs if I use the TextView's scrolling (android:scrollbars = "vertical" and .setMovementMethod(new ScrollingMovementMethod());
PSS Tried switching off persistent drawing cache, but no different dreaandroid:persistentDrawingCache="none"
Have you tried removing the scroll view whenever it's container view scrolls off the screen? I'm not sure if that works for you but its worth a shot? Alternatively, try calling setScrollContainer(false) on the scroll view when it leaves the screen. That seems to remove the view from the mScrollContainers set.
Also, this question, answered by Dianne Hackborn (android engineer), explicitly states not to use scrollable views inside of a Gallery. Maybe this issue is why?
Just add this -> android:isScrollContainer="false"
<ScrollView
android:id="#+id/description_scroller"
android:layout_width="fill_parent"
android:layout_height="wrap_content"
android:scrollbars="vertical"
android:fillViewport="true"
android:isScrollContainer="false">
There is some source why this is appear:
http://grepcode.com/file/repository.grepcode.com/java/ext/com.google.android/android/4.0.1_r1/android/view/View.java
the problem is:
setScrollContainer(boolean isScrollContainer)
by default:
boolean setScrollContainer = false;
but in some cases like this
if (!setScrollContainer && (viewFlagValues&SCROLLBARS_VERTICAL) != 0) {
setScrollContainer(true);
}
it can be true, and when it happends
/**
* Change whether this view is one of the set of scrollable containers in
* its window. This will be used to determine whether the window can
* resize or must pan when a soft input area is open -- scrollable
* containers allow the window to use resize mode since the container
* will appropriately shrink.
*/
public void setScrollContainer(boolean isScrollContainer) {
if (isScrollContainer) {
if (mAttachInfo != null && (mPrivateFlags&SCROLL_CONTAINER_ADDED) == 0) {
mAttachInfo.mScrollContainers.add(this);
mPrivateFlags |= SCROLL_CONTAINER_ADDED;
}
mPrivateFlags |= SCROLL_CONTAINER;
} else {
if ((mPrivateFlags&SCROLL_CONTAINER_ADDED) != 0) {
mAttachInfo.mScrollContainers.remove(this);
}
mPrivateFlags &= ~(SCROLL_CONTAINER|SCROLL_CONTAINER_ADDED);
}
}
mAttachInfo.mScrollContainers.add(this) - all view put into ArrayList this lead to leak of memory sometimes
Yes i noticed the problem, sorry for my previous comment, i've tried to empty the Drawables
by setting previous Drawable.setCallBack(null); but didnt work, btw i have nearly the same project, i use ViewFlipper instead of Gallery, so i can control every thing, and i just use 2 Views in it, and switch between them, and no memory leak, and why not you resize the Image before displaying it, so it will reduce memory usage (search SO for resizing Image before reading it)
Try moving "android:layout_below="#id/slideshow_title" in TextView to ScrollView.
Ended up with implementing a workaround that uses a TextSwitcher that is automatically changed to the remaining substring every x seconds.
Here is the relevant xml definition from the layout
<TextSwitcher
android:id="#+id/slideshow_description"
android:textSize="#dimen/description_font_size"
android:layout_width="wrap_content"
android:layout_height="wrap_content">
<TextView
android:id="#+id/slideshow_description_anim1"
android:textSize="#dimen/description_font_size"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:maxLines="2"
android:textColor="#color/white"
android:singleLine="false"/>
<TextView
android:id="#+id/slideshow_description_anim2"
android:textSize="#dimen/description_font_size"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:maxLines="2"
android:textColor="#color/white"
android:singleLine="false"/>
</TextSwitcher>
Here I add the transition animation to the TextSwitcher (in the adapter's getView method)
final TextSwitcher slideshowDescription = (TextSwitcher)slideshowView.findViewById(R.id.slideshow_description);
Animation outAnim = AnimationUtils.loadAnimation(context,
R.anim.slide_out_down);
Animation inAnim = AnimationUtils.loadAnimation(context,
R.anim.slide_in_up);
slideshowDescription.setInAnimation(inAnim);
slideshowDescription.setOutAnimation(outAnim);
Here is how I swap to the part of the description
private void updateScrollingDescription(SlideshowPhoto currentSlideshowPhoto, TextSwitcher switcherDescription){
String description = currentSlideshowPhoto.getDescription();
TextView descriptionView = ((TextView)switcherDescription.getCurrentView());
//note currentDescription may contain more text that is shown (but is always a substring
String currentDescription = descriptionView.getText().toString();
if(currentDescription == null || description==null){
return;
}
int indexEndCurrentDescription= descriptionView.getLayout().getLineEnd(1);
//if we are not displaying all characters, let swap to the not displayed substring
if(indexEndCurrentDescription>0 && indexEndCurrentDescription<currentDescription.length()){
String newDescription = currentDescription.substring(indexEndCurrentDescription);
switcherDescription.setText(newDescription);
}else if(indexEndCurrentDescription>=currentDescription.length() && indexEndCurrentDescription<description.length()){
//if we are displaying the last of the text, but the text has multiple sections. Display the first one again
switcherDescription.setText(description);
}else {
//do nothing (ie. leave the text)
}
}
And finally, here is where I setup the Timer which causes it to update every 3.5 seconds
public void setUpScrollingOfDescription(){
final CustomGallery gallery = (CustomGallery) findViewById(R.id.gallery);
//use the same timer. Cancel if running
if(timerDescriptionScrolling!=null){
timerDescriptionScrolling.cancel();
}
timerDescriptionScrolling = new Timer("TextScrolling");
final Activity activity = this;
long msBetweenSwaps=3500;
//schedule this to
timerDescriptionScrolling.scheduleAtFixedRate(
new TimerTask() {
int i=0;
public void run() {
activity.runOnUiThread(new Runnable() {
public void run() {
SlideshowPhoto currentSlideshowPhoto = (SlideshowPhoto)imageAdapter.getItem(gallery.getSelectedItemPosition());
View currentRootView = gallery.getSelectedView();
TextSwitcher switcherDescription = (TextSwitcher)currentRootView.findViewById(R.id.slideshow_description);
updateScrollingDescription(currentSlideshowPhoto,switcherDescription);
//this is the max times we will swap (to make sure we don't create an infinite timer by mistake
if(i>30){
timerDescriptionScrolling.cancel();
}
i++;
}
});
}
}, msBetweenSwaps, msBetweenSwaps);
}
Finally I can put this problem to a rest :)
I'm seeing drawing issues on Honeycomb that I can't figure out. For some reason when I add buttons to a view in code the background of the view disapears (becomes black).
This works fine on my Galaxy tab running 2.3. It fails in the emulator or on my Motorolla Xoom running 3.2.
Details:
In onCreate() I'm setting either a background color or a background image on the relativeLayout defined in main.xml. My relativeLayout is defined as fill_parent.
I have an OnTouchListener for my activity, in which I add two green buttons (one left aligned, one right aligned), to the relativeLayout.
When I add those two buttons the background of the relativeLayout disappears (shows all black). I can't explain why.
Clues:
If I set the color of one of the green buttons to instead be Color.TRANSPARENT, everything works and my background doesn't disappear. This seems like a big clue, but I can't figure out what it means.
When using color background, if I set the targetSdkVersion to "11" instead of "7" (I'm targetting 2.1/7) it works and my color background doesn't disappear. But images are still broken.
When using color background, I can call setDrawingCacheBackgroundColor( Color.RED ) which results in a red background instead of black. I could use this as a solution, setting the cache color to my background color, but this doesn't work when using images.
This really feels like a bug in android because I can't see that I'm doing anything wrong in my code. I'd appreciate any help or advice.
Here's my simple main.xml layout:
<?xml version="1.0" encoding="utf-8"?>
<RelativeLayout
xmlns:android="http://schemas.android.com/apk/res/android"
android:layout_width="fill_parent"
android:layout_height="fill_parent"
android:id="#+id/MyLayout">
</RelativeLayout>
Here's my activity code:
public class ViewTesterActivity extends Activity {
#Override
public void onCreate(Bundle savedInstanceState) {
super.onCreate( savedInstanceState );
// set the relative layout as our view
setContentView( R.layout.main );
// get the relative layout
android.widget.RelativeLayout layout = (android.widget.RelativeLayout) findViewById( R.id.MyLayout );
// set an on touch listener
layout.setOnTouchListener( (android.view.View.OnTouchListener) mOnTouchListener );
// set a background image
// PROBLEM: this background image disappears when adding the buttons in our touch handler
// GOOFY FIX: If I change the color of one of the buttons we add to be Color.TRANSPARENT, this
// background image doesn't disappear.
layout.setBackgroundResource( R.drawable.icon );
layout.setDrawingCacheEnabled( false );
// alternatively, set background color
// PROBLEM: the color disappears (becomes black) when adding the button in our touch handler
// GOOFY FIX: If I change the color of one of the buttons we add to be Color.TRANSPARENT, the color
// doesn't disappear
// GOOFY FIX 2: If I set android:targetSdkVersion to "11" in our manifest, the color doesn't disappear
// CLUE?: Without any fixes above and using color as the background, if I set the drawing cache color
// to Color.RED it'll show up instead of black. This would be a great solution to my problem but it
// doesn't work for background images.
// layout.setDrawingCacheBackgroundColor( Color.RED );
// layout.setDrawingCacheEnabled( true );
}
// on touch listener, add two buttons to the view
private android.view.View.OnTouchListener mOnTouchListener = new android.view.View.OnTouchListener() {
public boolean onTouch(View v, MotionEvent event) {
if ( v != null )
v.onTouchEvent( event );
if ( event.getAction() == MotionEvent.ACTION_UP ) {
android.widget.RelativeLayout layout = (android.widget.RelativeLayout) findViewById( R.id.MyLayout );
// add a green button, left aligned
Button btn = new Button( ViewTesterActivity.this );
btn.setBackgroundColor( Color.GREEN );
// GOOFY FIX: setting this instead of green solves the issue, no disappearing background
// btn.setBackgroundColor( Color.TRANSPARENT );
RelativeLayout.LayoutParams params = new RelativeLayout.LayoutParams( 100, 100 );
params.addRule( RelativeLayout.ALIGN_PARENT_LEFT );
layout.addView( btn, params );
// add a green button, right aligned
btn = new Button( ViewTesterActivity.this );
btn.setBackgroundColor( Color.GREEN );
params = new RelativeLayout.LayoutParams( 100, 100 );
params.addRule( RelativeLayout.ALIGN_PARENT_RIGHT );
layout.addView( btn, params );
}
return true;
}
};
}
I faced a somehow similar problem, and I got it working by using handlers. You need to send a message to a handler implemented in your activity and invalidate your view. There is no need for setDrawingCacheEnabled( false ). Here is what I did for my problem:
protected static final int REFRESH = 0;
private Handler _hRedraw;
public void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
...
_hRedraw=new Handler(){
#Override
public void handleMessage(Message msg)
{
switch (msg.what) {
case REFRESH:
redrawEverything();
break;
}
}
};
}
private void redrawEverything()
{
android.widget.RelativeLayout layout = (android.widget.RelativeLayout) findViewById( R.id.MyLayout );
layout.invalidate();
layout.refreshDrawableState();
}
Now in your OnTouchListener just send a message to your handler using:
_hRedraw.sendEmptyMessage(REFRESH);