I'm using android.support.v4.widget.SwipeRefreshLayout with android.support.v7.widget.RecyclerView.
On fragment view creation I need to show SwipeRefreshLayout animation. But when I call setRefreshing(true) nothing happens. But when I refresh data animation changes.
I suppose that the animation isn't showing without child inside SwipeRefreshLayout.
How to show this animation in the best way?
This is a reported bug (reported here) and a workaround is available:
mSwipeRefreshLayout.setProgressViewOffset(false, 0,
(int) TypedValue.applyDimension(
TypedValue.COMPLEX_UNIT_DIP,
24,
getResources().getDisplayMetrics()));
mSwipeRefreshLayout.setRefreshing(true);
It depends on which API level you're building under - if you're using up to API 20 then you can just turn on setRefreshing(true), this will run the animation in the ActionBar, but in API 21 (Material Design) they changed the progress to be a spinner than is "pulled into view" before it spins
You have 2 ways of getting around this in API 21:
1) shift the spinner down with setProgressViewOffset(), but remember to shift it back up afterwords (note that this works in px, while setDistanceToTriggerSync() uses dp)
2) make a duplicate spinner that is displayed when you're loading the data
The more code-efficient solution is to use the existing spinner, but you have to be careful that you do reset its position
If you need to calculate the pixel density, you can grab it from:
DisplayMetrics metrics = new DisplayMetrics();
activity.getWindowManager().getDefaultDisplay().getMetrics(metrics);
float scale = metrics.density;
Thanks, guys.
I`ve just realised that the easiest solution is to call method measure(int, int) with non-zero values) before calling setRefreshing(true). The problem is happening because setRefreshing(true) is calling before onMeasure.
So you need to call:
swipeRefreshLayout.measure(initialWidth, initialHeight);
swipeRefreshLayout.setRefreshing(true);
SwipeRefreshLayout must contain a child to work properly. Make sure you initialize it before using it anywhere!
you must call setRefreshing(true) after onDraw(), else it would not display animation.
so, in Activity, you can do it in onCreate(), like this:
#Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
// other codes
new Handler().post(new Runnable() {
#Override
public void run() {
swipeRefreshLayout.setRefreshing(true);
refreshing = true;
refreshData();
}
}
}
in fragment, you can put these codes in onActivityCreated().
Related
I have an activity A. I am creating a kind of tutorial for user for this activity, to teach him how he can use the app on that screen.
For that, my requirement is :
I want to blur all the views of the activity except one view. I want to prompt user to click on that view through a hand image pointing at that view.
Nothing should happen if the user clicks on the blurred/greyed out area, but if he taps on that particular active view, it should react to that touch.
I was thinking of using a full screen fragment for this. The Fragment will take the following input from the activity :
for what coordinates, is should not blur the screen and pass the touch event to the activity
the coordinates on which it should show that pointing hand image.
After from these coordinates, the fragment background would be blur.
I wanted to confirm if that's possible, to make the fragment partially active, i.e. delegate it's touch events to the activity for a particular view of the activity.
Also, please let me know if there is any other better approach of achieving the same thing.
Edit1 :
Thinking of using a fragment here, because I'd want this type of behaviour on different screen in future. In that case, I'd make that fragment generic which takes some inputs (as described above) and use it on different screens.
There's a very good library called SCV which does what you're trying to achieve, you're able to customize the styles for it too. I've used this for first time the app is opened to show the user a tutorial.
According to their Github
The ShowcaseView (SCV) library is designed to highlight and showcase specific parts of apps to the user with a distinctive and attractive overlay. This library is great for pointing out points of interest for users, gestures, or obscure but useful items.
Further Reading:
Android Arsenal - Showcase Views Tutorial
ShowCaseView on Android - Indipendev
I found it much easier to include an 'extra' layout around the UI of my activity, and then to add a highest-z grey mostly-transparent filter to it and put the instructions on that.
Each "step" of the instructions was a different layout that was dynamically loaded into that layout container as they clicked. (Just another approach)
The 'container' layout is a: FrameLayout
then in my Activity I have: (ignore bad naming)
private void addOverlayLayout() {
frameLayout = (FrameLayout) findViewById(R.id.framelayoutInner);
frameLayout3 = (FrameLayout) findViewById(R.id.framelayout3);
frameLayout3.setBackgroundColor(Color.DKGRAY);
frameLayout3.setAlpha(0.3f);
// Dynamically create a relativelayout which will be appended to framelayout
relativeLayout = new RelativeLayout(getApplicationContext());
relativeLayout.setLayoutParams(new RelativeLayout.LayoutParams(ViewGroup.LayoutParams
.MATCH_PARENT, ViewGroup.LayoutParams.MATCH_PARENT));
instructionOverlays.add(createSimpleClickInstruction(R.layout.instruction_reader_1));
instructionOverlays.add(createSimpleClickInstruction(R.layout.instruction_reader_2));
if (FullscreenReaderActivity.isFirstRun) {
displayNextGuide();
}
}
public void displayNextGuide() {
// clean relative layout if it has views
relativeLayout.removeAllViews();
// clean frame layout if it has child (safe if empty)
frameLayout.removeView(relativeLayout);
if (!isFirstRun) {
return;
}
if (instructionOverlays.size() > 0) {
runOnUiThread(instructionOverlays.get(0));
instructionOverlays.remove(0);
} else {
frameLayout3.setBackgroundColor(Color.TRANSPARENT);
frameLayout3.setAlpha(1.0f);
}
}
public Runnable createSimpleClickInstruction(final int resource) {
return new Runnable() {
#Override
public void run() {
getLayoutInflater().inflate(
resource,
relativeLayout,
true
);
relativeLayout.setOnClickListener(new View.OnClickListener() {
#Override
public void onClick(View v) {
displayNextGuide();
}
});
frameLayout.addView(relativeLayout);
}
};
}
I am working on an Android app that runs on only one devicerunning KitKat.
The smooth scrolling feature for a RecylerView I used that was working on other physical tablets and genymotion has unfortunately stopped working on the one device it needs to work on.
Instead of scrolling to a certain position it passes over the target position and scrolls all the way to the bottom and looks really bad.
I am able to track down the error to the abstract SmoothScroller in the RecyclerView class.
if (getChildPosition(mTargetView) == mTargetPosition) {
onTargetFound(mTargetView, recyclerView.mState, mRecyclingAction);
mRecyclingAction.runIfNecessary(recyclerView);
stop();
} else {
Log.e(TAG, "Passed over target position while smooth scrolling.");
mTargetView = null;
}
I was using a SnappingLinearLayoutManager that I found online, but swapped it out with the normal LinearLayoutManager from Android, and still am having the same problem.
The list is 7 items long (user can see 4 at a time) and I scroll to the 5th item (position 4) item.
When I scroll to the 3rd I don't receive this error.
Also after I scroll the list up and down once, the error stops happening.
EDIT:
I am able to use layoutManager.scrollToPositionWithOffset(); But I am trying to do this with the smooth scroll animation.
Here is some of my code and details:
private void setupMainRecyclerViewWithAdapter() {
mainLayoutManager = new SnappingLinearLayoutManager(this, LinearLayoutManager.VERTICAL, false);
mainListRecyclerView.setLayoutManager(mainLayoutManager);
settingsMainListAdapter = new SettingsListAdapter(SettingsActivity.this,
settingsPresenter.getSettingsItems(),
settingsPresenter);
mainListRecyclerView.setAdapter(settingsMainListAdapter);
mainListRecyclerView.addItemDecoration(new BottomOffsetDecoration(EXTRA_VERTICAL_SCROLLING_SPACE));
}
#Override
public void scrollMainList(boolean listAtTop) {
if(listAtTop) {
mainListRecyclerView.smoothScrollToPosition(4);
moveMainMoreButtonAboveList();
} else {
mainListRecyclerView.smoothScrollToPosition(0);
moveMainMoreButtonBelowList();
}
}
If you call recyclerView.smoothScrollToPosition(pos) will be called immediately on the UI thread and if recyclerView's Adapter is too much busy to generating view items then the calling of smoothScrollToPosition will be missed then because recyclerView has no data to smooth scroll. So it's better to do that in a background thread by recyclerView.post(). By calling this it goes into the Main thread queue and gets executed after the other pending tasks are finished.
Therefore you should do something like this which worked for my case:
recyclerView.post(new Runnable() {
#Override
public void run() {
recyclerView.smoothScrollToPosition(pos);
}
});
Well, I realize it's too late, however I tried some different solutions and found one...
in custom LinearSmoothScroller I override updateActionForInterimTarget
#Override
protected void updateActionForInterimTarget(Action action) {
action.jumpTo(position);
}
It's appears not very smooth, but not instant in contrast with scrollToPositionWithOffset.
Just add one line for smooth scroll
recyclerView.setNestedScrollingEnabled(false);
it will work fine
Take a look at hasPendingAdapterUpdates(). You can use this along with a delay() for coroutines or Thread.sleep() to enable the backing data to be available before doing the scroll.
This question already has answers here:
Android SeekBar set progress value
(10 answers)
Closed 4 years ago.
I am using a Seekbar in a fragment and need to move the Seekbar to different positions. However, setProgress(xxx) is not working.
How do you trigger this method programmatically for a Seekbar: public void onProgressChanged(SeekBar seekBar, int progress, boolean fromUser)? Moving the Seekbar manually works fine.
It's a Bug in ProgressBar!
The setProgress(...) seems to not trigger the update on the drawable if the same value is passed again. But it's not triggered during the setMax, too. So the update is missing.
To solve this, I'm just doing a bar.setProgress(0) before each update... this is only a workaround, but it works for me as expected:
bar.setProgress(0); // call these two methods before setting progress.
bar.setMax(20);
bar.setProgress(20);
Second Option.
mSeekBar.post(new Runnable() {
#Override
public void run() {
mSeekBar.setProgress(percentOfFullVolume);
}
});
it may also work for someone.
Try to set max value first and then set the progress.
I add this answer as this can help someone in the future.
In my case, I also thought setProgress was not working properly but here is my mistake.
increaseIncomeSeekBar.setProgress(progress);
increaseIncomeSeekBar.setMax(maxProgress);
For info progress and maxProgress equals 2000 and 12000. But when displaying the SeekBar, it seems like the progress was at 0 and thus not working properly.
You have to be aware of this which is pretty logical when you know it:
first SeekBar max = 100.
when you set a progress > max then progress = max.
so you need to set a max value before setting a progress value.
I add a comment to this topic because it happenned to me and it took me hours to figure out what the problem was. The bug is still active and the setProgress() method does not work properly sometimes.
I have a MainActivity which commit and replace Fragments and transmit the SeekBar value inside the bundle of each Fragments. The User changes the value of the Seekbar and its progress are put in the Bundle. Then when the User switch of Fragment, the new one get the SeekBar progress.
It works perfectly fine to transmit it this way and I always have the right value on my progress variable in the second Fragment. The problem appears when I use setProgress(theProgressValueTransmitted) method. This set my SeekBar progress only the first time I replace the first Fragment. After that, it never changes it wheareas the value of progress is still the right one.
I used :
int seekBarProgress;
Bundle bundle = this.getArguments();
if (bundle != null) {
seekBarProgress = bundle.getInt("seekBarProgress");
mySeekBar.post(new Runnable() {
#Override
public void run() {
eventListTimeSeekBar.setProgress(seekBarProgress);
}
});
}
And this is the only way I can make this work. Hope it could help someone with the same problem. This post is more than 4 years old, I don't even understand how this bug can still exist since it has been reported.
SeekBar.setProgress() should work just fine. Are you sure your code is executing on the UI thread? If not, then this would be the obvious explanation. Check the example in the API doc for ProgressBar
https://developer.android.com/reference/android/widget/ProgressBar.html
There they show how to bring the execution back to the main thread.
Nowadays everything seems to work as expected.
Post is not needed anymore.
Just do:
seekBar.setMax(200);
seekBar.setProgress(50);
(order doesn't matter)
Most of the time, SeekBar works fine. Sometimes, it won't.
#Override
public View onCreateView(LayoutInflater inflater, ViewGroup container,
Bundle savedInstanceState) {
//...
seekBar.setProgress(value);
//...
}
After I refresh the fragment view by detach() + attach(), the code does not work. My solution is to apply setProgress() within onViewStateRestored().
#Override
public void onViewStateRestored(Bundle inState) {
//...
seekBar.setProgress(value);
//...
}
EDIT: tl;dr: WebView appears as white box, even though I appear to be setting it up correctly, and indeed it does work the first two times, but fails subsequently)
EDIT: Video showing the problem in action...
I have the following bit of code which inflates a view (Which contains a WebView) from the xml which defines it:
private void createCard(ViewGroup cvFrame, Card card) {
//... setup vairables...
cvFrame.clearDisappearingChildren();
cvFrame.clearAnimation();
try {
View cv = LayoutInflater.from(getBaseContext()).inflate(R.layout.card_back_view,
cvFrame, true);
cv.setBackgroundDrawable(Drawable.createFromStream(mngr.open(deckName + "_Card_back.png"), deckName));
TextView suit = (TextView)cv.findViewWithTag("card_back_suit");
//...setup text view for suit, this code works fine every time...
WebView title = (WebView)cv.findViewWithTag("card_back_title");
//This WebView doesn't appear to be the one which actually appears on screen (I can change settings till I'm blue in the face, with no effect)
if (title != null) {
title.setBackgroundColor(0x00000000);
title.loadData(titleText, "text/html", "UTF-8");
} else {
Log.e("CardView", "Error can't find title WebView");
}
} catch (IOException e) {
Log.e("CardView", "Error making cards: ", e);
}
}
When this method is called as part of the onCreate method in my Activity, the WebView contains the correct code, and is suitably transparent.
I have a gesture listener which replaces the contents of the ViewGroup with different content (It animates the top card off to the left, replaces the contents of the top card with card 2, puts the top card back, then replaces card 2 with card 3)
//Gesture listener event
ViewGroup cvFrame = (ViewGroup)findViewById(R.id.firstCard);
cardLoc++
cvFrame.startAnimation(slideLeft);
(onAnimationEnd code)
public void onAnimationEnd(Animation animation) {
if (animation == slideLeft) {
ViewGroup cvFrameOldFront = (ViewGroup)findViewById(R.id.firstCard);
ViewGroup cvFrameNewFront = (ViewGroup)findViewById(R.id.secondCard);
createCard(cvFrameOldFront, cards.get((cardLoc)%cards.size()));
createCard(cvFrameNewFront, cards.get((cardLoc+1)%cards.size()));
TranslateAnimation slideBack = new TranslateAnimation(0,0,0,0);
slideBack.setDuration(1);
slideBack.setFillAfter(true);
cvFrameOldFront.startAnimation(slideBack);
}
}
When the animation has happened and I replace the contents of the cards, the TextView suit is replaced fine and the code definitely passes through the code to replace the WebView contents, but for some reason I end up with a white rectangle the size and shape of the WebView, no content, no transparency.
If I change the WebView to a TextView, it's contents is replaced fine, so it's an issue that occurs only with the WebView control :S
Can anyone tell me why / suggest a fix?
It turns out the WebView doesn't get cleared down when using the LayoutInflater to replace the contents of a ViewGroup. The other controls all seem to get removed (or at least the findViewWithTag() returns the right reference for every other control). I've just added in the line cvFrame.removeAllViews() immediately before the LayoutInflater does it's stuff and that fixed the issue.
If anyone has any better explanation for this I'll throw the points their way otherwise they will just go into the ether...
By calling findViewById, you are getting a reference on the previously loaded webview do you ?
so the loadData call that fails is the second one you make on a single webview instance.
you may want to check this :
Android WebView - 1st LoadData() works fine, subsequent calls do not update display
It appears that loadData() won't load data twice... you may want to try WebView.loadDataWithBaseUri()
Hope that helps.
I had a similar problem loading several WebViews content.
It was because of a misusing of the pauseTimers function
The situation was : the first webView weren't needed anymore, conscientiously I wanted to pause it before to release it. Calling onPause() and pauseTimers()
pauseTimers being common to any web views, it broke every use of webviews occuring after that, there were displaying only white rectangles.
Maybe its not your problem here, but it's worth checking your not calling WebView.pauseTimers() somewhere.
To confirm your answer, the source code for LayoutInflater.inflate(int resource, ViewGroup root, boolean attachToRoot) does in fact internally calls root.addView() which attaches the newly inflated view at the end of the root's children instead of replacing them.
So the mystery now is why did your call to findViewWithTag() is returning the expected objects for your other widgets (which would be the top, most recently created instances), but for your WebView it was returning something else.
Is it possible that there is another object in your layout XML which shares the same "card_back_title" tag?
I was also wondering why you didn't use the more common findViewById() instead, but I am not sure whether it would make a difference.
I have a ListView with about 100 entries. When the user does the "fling" from bottom to top it starts scrolling and keeps on scrolling even when the finger does not touch the display any more.
Is there a way to stop the scrolling animation at this point?
and we lookup the android source code (AbsListView), give it a ACTION_CANCEL touchEvent, can stop the fling. it is easy.
listView.dispatchTouchEvent(MotionEvent.obtain(SystemClock.uptimeMillis(), SystemClock.uptimeMillis(), MotionEvent.ACTION_CANCEL, 0, 0, 0));
I didn't try the solution of Pompe de velo but since smoothScrollToPosition() is not available for API level less than 8 this didnt work for me.
I agree, changing default behaviour is not a good Idea, but sometimes you need to. So here is my (dirty) solution which uses reflection. This is by far not the recommended way since it's a hack but it works for me. There might be a better solution but I didn't found it.
class StopListFling {
private static Field mFlingEndField = null;
private static Method mFlingEndMethod = null;
static {
try {
mFlingEndField = AbsListView.class.getDeclaredField("mFlingRunnable");
mFlingEndField.setAccessible(true);
mFlingEndMethod = mFlingEndField.getType().getDeclaredMethod("endFling");
mFlingEndMethod.setAccessible(true);
} catch (Exception e) {
mFlingEndMethod = null;
}
}
public static void stop(ListView list) {
if (mFlingEndMethod != null) {
try {
mFlingEndMethod.invoke(mFlingEndField.get(list));
} catch (Exception e) {
}
}
}
}
Well there surely is a way to do it. But the point is more whether or not it is advisable to do it, in my opinion.
The list is a standard Android control that behaves constistently across all applications. So I would be surprised if I found a list that did not behave the same in your application. You can stop the fling by putting your finger back on the screen at any time.
That said, if you want to do extra work, you could subclass the list view and override its on touch method. Best way to know what to do is to get the source code of ListView (ListView in Android 1.6).
You can prevent flinging for ListViews in API 8 by overriding onTouchEvent and calling smoothScrollBy.
#Override
public boolean onTouchEvent(MotionEvent ev) {
switch (ev.getAction()) {
case MotionEvent.ACTION_UP:
this.smoothScrollBy(0, 0);
break;
}
return super.onTouchEvent(ev);
}
This takes over from the fling scrolling and scrolls 0px instead.
My opinion is that you shouldn't modify this behaviour, since the fling behaviour is what the user expects.
However, to your question. I haven't tried this but in theory it should work.
Implement an OnScrollListener to your ListView and use the onScrollStateChanged() method to check if the current state is SCROLL_STATE_FLING. After you've determined that the scrolling perfoms by a fling you can get your ListView's first visible position by using the getFirstVisiblePosition() method and from there you can use smoothScrollToPosition() where you put in your getFirstVisiblePosition() value as an argument.
if you what disable default animation from list view just need set id for root (main) layout in xml and call void onClickListener in class for root layout