I have a problem where in a specific situation when I call setVisibility(GONE) inside my custom view, its onVisibilityChanged method doesn't get called and it actually doesn't hide the view although getVisibility() returns 8 (or GONE) afterwards.
Here is how I know the visibility changes but onVisibilityChanged is not called.
#Override
protected void onVisibilityChanged(#NonNull View changedView, int visibility) {
Log.i(LOG_TAG, "onVisibilityChanged: " + visibility);
super.onVisibilityChanged(changedView, visibility);
}
#Override
public void setVisibility(int visibility) {
super.setVisibility(visibility);
Log.i(LOG_TAG, "setVisibility: " + visibility);
}
public void hide(){
Log.i(LOG_TAG, "before hide visibility: " + getVisibility());
setVisibility(GONE);
Log.i(LOG_TAG, "after hide visibility: " + getVisibility());
}
Normally when I call hide() I see these lines in the log:
before hide visibility: 0
onVisibilityChanged: 8
setVisibility: 8
after hide visibility: 8
But in a spicific situation when I call hide() I get these lines in the log and the view isn't hidden afterwards although getVisibility() returns 8:
before hide visibility: 0
setVisibility: 8
after hide visibility: 8
So when in general does this happen? When does setVisibility not call onVisibilityChanged?
Don't ask what my specific situation is. But please provide every general situation where this might happen.
It is called only when the view is attached in the hierarchy.
The call to setVisibility looks like this:
public void setVisibility(#Visibility int visibility) {
setFlags(visibility, VISIBILITY_MASK);
}
The setFlags method is a long one where a bunch of different view properties are changed and handled, but the noticable part is this:
if ((changed & VISIBILITY_MASK) != 0) {
// if visiblity changed...
...
if (mAttachInfo != null) { // true if attached in view hierarchy
dispatchVisibilityChanged(this, newVisibility); // onVisibilityChanged is called from here
...
So you will see your described behaviour on a view that's not attached to a fragment or activity.
you can use another way to solve this problem
create a method like this
private void stateCheck(View view){
if (view.getVisibility()==View.GONE){
// handle gone state
}else if (view.getVisibility()==View.INVISIBLE){
// handle invisible state
}else{
// handle other states
}
}
View.onVisibilityChanged
Called when the visibility of the view or an ancestor of the view has
changed.
As this says, onVisibilityChanged will only be called when visibility is changed.
public void hide(){
setVisibility(GONE);
}
This will be called in two cases.
When visibility is VISIBLE.
When visibility is INVISIBLE.
This will not be called if View visibility is already set to GONE.
Solution
So how can you capture all the events of visibility?
Override setVisibility method in your custom view class. This method will be called every time visibility is called. Like below
public class CustomTextView extends AppCompatTextView {
#Override
public void setVisibility(int visibility) {
super.setVisibility(visibility);
System.out.println("CustomTextView.setVisibility " + visibility);
}
}
Update
I could not get this situation. But here is a workaround for getting rid of your problem.
If you are sure, that your code either calls onVisibilityChanged or setVisibility when visibility is changed. Then you can do this.
customTextView.setOnVisibilityChange(new CustomTextView.OnVisibilityChange() {
#Override
public void onVisibilityChange(int visibility) {
}
});
Here is the sample view class.
public class CustomTextView extends AppCompatTextView {
private int currentVisibility = 0;
OnVisibilityChange onVisibilityChange;
public void setOnVisibilityChange(OnVisibilityChange onVisibilityChange) {
this.onVisibilityChange = onVisibilityChange;
}
#Override
protected void onVisibilityChanged(View changedView, int visibility) {
super.onVisibilityChanged(changedView, visibility);
callback(visibility);
}
#Override
public void setVisibility(int visibility) {
super.setVisibility(visibility);
callback(visibility);
}
private void callback(int visibility) {
if (currentVisibility != visibility && onVisibilityChange != null)
onVisibilityChange.onVisibilityChange(visibility);
currentVisibility = visibility;
}
public interface OnVisibilityChange {
void onVisibilityChange(int visibility);
}
}
Related
My app contain a bottomsheeet and i am using following methods to change its state
public void toggleBottomSheet() {
if (sheetBehavior.getState() != BottomSheetBehavior.STATE_EXPANDED) {
sheetBehavior.setState(BottomSheetBehavior.STATE_EXPANDED);
} else {
sheetBehavior.setState(BottomSheetBehavior.STATE_COLLAPSED);
}
}
But in some devices it is not showing animation properly ( Bottom to up ), It opens directly like popup.
How to add animation so it looks it is coming from bottom.
Not sure about this point of "in some devices", can you please mention the android version and other specifications? My suggestion is to add a BottomSheetBehavior.BottomSheetCallback to the BottomSheetBehavior
and in its onSlide method put a log.d() to check if its really sliding. It will help to find out the cause. Also, make sure that you have added the right BootmSheetBehavior and you can try by setting the peeckHeight explicitly.
yourBottomSheetBehavior.setBottomSheetCallback(new BottomSheetBehavior.BottomSheetCallback()
{
#Override
public void onStateChanged(#NonNull View view, int newState)
{
if (newState == BottomSheetBehavior.STATE_EXPANDED)
{
}
}
#Override
public void onSlide(#NonNull View view, float v)
{
Log.d("tag", "sliding from bottom to top");
}
});
I'm trying to implement a full screen mode with SimpleExoPlayerView. I've got this mostly working using setSystemUiVisibility.
During onCreate i add a OnSystemUiVisibilityChange listener to sync hiding the player controls with the actionbar.
#Override
public void onCreate(Bundle savedInstanceState) {
View decorView = getWindow().getDecorView();
decorView.setOnSystemUiVisibilityChangeListener
(onSystemUiChange());
hideSystemUI();
}
In the OnSystemUiVisibilityChangeListener i'm also setting a timeout that matches the simpleExoplayerViews timeout so the controls and action bar are hidden at the same time.
#NonNull
private View.OnSystemUiVisibilityChangeListener onSystemUiChange() {
return new View.OnSystemUiVisibilityChangeListener() {
#Override
public void onSystemUiVisibilityChange(int visibility) {
if ((visibility & View.SYSTEM_UI_FLAG_FULLSCREEN) == 0) {
mSimpleExoPlayerView.showController();
final Handler handler = new Handler();
handler.postDelayed(new Runnable() {
#Override
public void run() {
//sync the hide system ui with
//simpleExoPlayerView's auto hide timeout
hideSystemUI();
}
}, mSimpleExoPlayerView.getControllerShowTimeoutMs());
} else {
mSimpleExoPlayerView.hideController();
}
}
};
}
private void hideSystemUI() {
View rootView = findViewById(R.id.root);
rootView.setSystemUiVisibility(
View.SYSTEM_UI_FLAG_LAYOUT_STABLE
| View.SYSTEM_UI_FLAG_LAYOUT_HIDE_NAVIGATION
| View.SYSTEM_UI_FLAG_LAYOUT_FULLSCREEN
| View.SYSTEM_UI_FLAG_HIDE_NAVIGATION // hide nav bar
| View.SYSTEM_UI_FLAG_FULLSCREEN // hide status bar
);
}
This works pretty well except in one case. If you tap the screen and then tap it again before the SimpleExoPlayerView controls timeout the SimpleExoPlayerView are hidden but the system ui do not get set until the timeout. Is there any events i can hook into instead?
I've tried setting a onClick and onTouch listener for my root layout but these events are not fired, i suspect SimpleExoPlayerView might be swallowing them?
ExoPlayer 2.10.4 has it.
exoplayer PlayerView has a method called
public void setControllerVisibilityListener(PlayerControlView.VisibilityListener listener) {
}
As of 2.6.1, SimpleExoPlayerView doesn't seem to have any visibility change listeners for the controls, but PlaybackControlView has. However, it's stored in a private field in SimpleExoPlayerView and there's no builtin way to a access it. To set your own listener, you'll either have to:
copy SimpleExoPlayerView.java to your project and make the required changes,
use reflection (don't forget to add proguard rules, if needed),
override exo_simple_player_view.xml and make sure it contains a PlaybackControlView, then find it using findViewById,
find it manually by traversing the view hierarchy.
In my opinion, the first and third options are the nicest, but the last one requires the least amount of changes, and it also works very well. Here is an example:
import com.google.android.exoplayer2.ui.PlaybackControlView;
import com.google.android.exoplayer2.ui.SimpleExoPlayerView;
public SomeActivity extends Activity implements PlaybackControlView.VisibilityListener {
private initExoPlayer() {
// ...
addPlaybackControlVisibilityListener(mSimpleExoPlayerView, this);
}
#Override
public void onVisibilityChange(int visibility) {
// show/hide system ui here
}
private static void addPlaybackControlVisibilityListener(SimpleExoPlayerView playerView, PlaybackControlView.VisibilityListener listener) {
PlaybackControlView playbackControlView = findPlaybackControlView(playerView);
if (playbackControlView != null)
playbackControlView.setVisibilityListener(listener);
}
private static PlaybackControlView findPlaybackControlView(ViewGroup viewGroup) {
for (int i = 0; i < viewGroup.getChildCount(); i++) {
View child = viewGroup.getChildAt(i);
if (child instanceof PlaybackControlView)
return (PlaybackControlView) child;
if (child instanceof ViewGroup) {
PlaybackControlView result = findPlaybackControlView((ViewGroup) child);
if (result != null)
return result;
}
}
return null;
}
}
With Exoplayer 2.16.1 you can use setControllerVisibilityListener like this:
viewBinding.playerView.setControllerVisibilityListener { visibility ->
if (visibility == View.VISIBLE) {
// controller is visible
} else {
// controller is not visible
}
}
There are two classes we have 1. PlayerView 2. StyledPlayerView.
I am answering here for StyledPlayerView since PlayerView is deprecated now.
First create a class which extends StyledPlayerView and also your Class do implement this interface class CustomPlayerView extends StyledPlayerView implements StyledPlayerView.ControllerVisibilityListener So you need to override onVisibilityChanged Method:
#Override
public void onVisibilityChanged(int visibility) {
isControllerVisible = visibility == View.VISIBLE;
}
Now you can call this method on some other class where all your playerView methods present
binding.playerView.setControllerVisibilityListener(customPlayerView)
So on Visibility change of your controls you will get callbacks.
My button use code that shows and hides the views:
public void onClick (View v){
if (What code you need to enter here to determine hidden views or shown)
{
testActivity.setVisibility(View.VISIBLE);
}
else
{
testActivity.setVisibility(View.GONE);
}
}
What code I need to add in the "if()", so that clicking on my button was checked condition. If the activity is hidden, it should be shown, and Vice versa. If the views is shown, hide it.
I'm guessing since you are using setVisibility, that you want to check the visibility of a View , not an Activity.
In that case you just use getVisibility()
(I used != cause the visibility could be IINVISIBLE as well, change per your needs) :
public void onClick (View v){
if (testActivity.getVisibility() != View.VISIBLE)
{
testActivity.setVisibility(View.VISIBLE);
}
else
{
testActivity.setVisibility(View.GONE);
}
} });
Don't understand why, but only that removed the answer of a man who has solved my problem. Here is his response, and this code works:
public void onClick (View v){
if ((testActivity.getVisibility() == View.VISIBLE))
{
testActivity.setVisibility(View.GONE);
}
else
{
testActivity.setVisibility(View.VISIBLE);
}
I have implemented a ListView that has the functionality that you see in many apps, where user scrolls to bottom and it loads more, that OnScrollListener is this:
public class OnScrolledToEndListener implements AbsListView.OnScrollListener
{
private int prevLast;
#Override
public void onScrollStateChanged(AbsListView absListView, int i)
{
}
#Override
public void onScroll(AbsListView absListView, int first, int visible, int total)
{
int last = first + visible;
if (last == total)
{
if (prevLast != last)
{
prevLast = last;
onScrolledToEnd();
}
}
}
public void onScrolledToEnd()
{
}
}
Now the problem is that when a user has scrolled to the bottom of a list, and hits the refresh button in my app, I want it to start over at the top of the list, because if it stays at the bottom of the list, then the scroll listener will immediately trigger. The best way I've found to solve this is by doing the following before executing the refresh:
mListView.setSelection(0);
mListView.post(
new Runnable()
{
#Override
public void run()
{
mListView.setVisibility(View.GONE);
mLoadingLayout.setVisibility(View.VISIBLE); //this is basically a progressbar
// do the refresh
}
}
);
But there is a slight flicker when the list scrolls to the top. Any ideas on how to make it look better?
I figured out the solution. Apparently setting the ListView to View.GONE makes it not update its layout, so I set it to View.INVISIBLE instead and it worked. I didn't even have to use a Runnable.
mListView.setSelection(0);
mListView.setVisibility(View.INVISIBLE);
mLoadingLayout.setVisibility(View.VISIBLE);
I have an Activity containing a ViewPager that displays N fragments. Each fragment is showing the properties of an object from an ArrayList in my ViewPager's custom adapter (extends FragmentStatePagerAdapter).
The fragment has (among other things) a button that should remove the currently displayed fragment and scroll to the next one with setCurrentItem(position, true) so that if the user scrolls back, the previous item is gone. I do so by using something like this (simplified):
deleteButton.setOnClickListener(new OnClickListener() {
#Override
public void onClick(View v) {
MyActivity parentActivity = (MyActivity)getActivity();
// First, scroll to next item (smoothly)
parentActivity.pager.setCurrentItem(parentActivity.pager.getCurrentItem()+1, true);
// Database stuff...
doSomeDBOperations();
// Method in Activity that removes the current object (I believe this method is working fine and yes, it calls notifyDataSetChanged())
parent.removeObject(currentObject);
}
});
This has the desired behavior as the object represented by the fragment whose delete button was pressed gets removed and the viewpager goes to the next page.
My problem is that the ViewPager doesn't scroll smoothly but rather "jumps instantly" to the next fragment. If I comment the removeObject() call, the smooth scroll works (but the item isn't removed). I believe it's has something to do with the removeObject() being called before the setCurrentItem() has finished the smooth scrolling animation?
Any ideas on how to fix this and achieve item removal + smooth scroll? If my assumption is correct, how can I make sure I get the smooth scroll to finish before removing the object?
EDIT 1:
My assumption seems correct. If I put the parent.removeObject(currentObject) inside
// ...inside the previously shown public void onClick(View v)...
confirm.postDelayed(new Runnable() {
#Override
public void run() {
// Method in Activity that removes the current object (I believe this method is working fine and yes, it calls notifyDataSetChanged())
parent.removeObject(currentObject);
}
}, 1000);
so that the removeObject() call waits for a second, it works as expected: scroll to the next item, remove the previous. But this is a very ugly workaround so I'd still like a better approach.
EDIT 2:
I figured out a possible solution (see below).
I ended up overriding the
public void onPageScrollStateChanged(int state)
method:
Whenever the user presses the delete button in the fragment, the listener sets a bool in the current item (flagging it for deletion) and scrolls to the next one.
When the onPageScrollStateChanged detects that the scroll state changed to ViewPager.SCROLL_STATE_IDLE (which happens when the smooth scroll ends) it checks if the previous item was marked for deletion and, if so, removes it from the ArrayList and calls notifyDataSetChanged().
By doing so, I've managed to get the ViewPager to smoothly scroll to the next position and delete the previous item when the "delete" button is pressed.
EDIT: Code snippet.
#Override
public void onPageScrollStateChanged(int state)
{
switch(state)
{
case ViewPager.SCROLL_STATE_DRAGGING:
break;
case ViewPager.SCROLL_STATE_IDLE:
int previousPosition = currentPosition - 1;
if(previousPosition < 0){
previousPosition = 0;
}
MyItem previousItem = itemList.get(previousPosition);
if(previousItem.isDeleted())
{
deleteItem(previousItem);
// deleteItem() Does some DB operations, then calls itemList.remove(position) and notifyDataSetChanged()
}
break;
case ViewPager.SCROLL_STATE_SETTLING:
break;
}
}
Have you tried ViewPager.OnPageChangeListener?
I would call removeObject(n) method in OnPageChangeListener.onPageSelected(n+1) method.
I did something different that works smoothly. The idea is to to remove the current item with animation (setting its alpha to 0), then translating horizontally the left or right item (with animation) to the now invisible item position.
After the animation is complete, I do the actual data removal and notfyDataSetChanged() call.
This remove() method I put inside a subclass of ViewPager
public void remove(int position, OnViewRemovedListener onViewRemovedListener) {
final int childCount = getChildCount();
if (childCount > 0) {
View toRemove = getChildAt(position);
int to = toRemove.getLeft();
final PagerAdapter adapter = getAdapter();
toRemove.animate()
.alpha(0)
.setDuration(getResources().getInteger(android.R.integer.config_shortAnimTime))
.setListener(new SimpleAnimatorListener() {
#Override
public void onAnimationEnd(Animator animation) {
if (childCount == 1) {
if (onViewRemovedListener != null) onViewRemovedListener.onRemoved(position, -1);
if (adapter!= null) adapter.notifyDataSetChanged();
}
}
})
.start();
if (childCount > 1) {
int newPosition = position + 1 <= childCount - 1 ? position + 1 : position - 1;
View replacement = getChildAt(newPosition);
int from = replacement.getLeft();
replacement.animate()
.setInterpolator(new DecelerateInterpolator())
.setDuration(getResources().getInteger(android.R.integer.config_mediumAnimTime))
.translationX(to - from)
.setListener(new SimpleAnimatorListener() {
#Override
public void onAnimationEnd(Animator animation) {
if (onViewRemovedListener != null) onViewRemovedListener.onRemoved(position, newPosition);
if (adapter!= null) adapter.notifyDataSetChanged();
}
})
.start();
}
}
}
public interface OnViewRemovedListener {
void onRemoved(int position, int newPosition);
}