So I'm having trouble with resizing an Imageview dynamically. I'm working with the Spotify SDK and for the album art I want the image to resize according to the bottom sheet scroll offset.
First of all, I can only post 1 link so here's an album with screenshots
So the image on the bottom left (first image in link) should resize to the second image in the link. It's a simple imageview in the bottom sheet (frame layout), this is my code:
mBottomSheetBehaviour.setBottomSheetCallback(new BottomSheetBehavior.BottomSheetCallback() {
#Override
public void onStateChanged(#NonNull View bottomSheet, int newState) {
int state = newState == BottomSheetBehavior.STATE_EXPANDED ? View.VISIBLE : View.GONE;
mCloseSheetButton.setVisibility( state );
}
#Override
public void onSlide(#NonNull View bottomSheet, float slideOffset) {
// Animate fab
int width = bottomSheet.getWidth();
int translationX = (int) ((-width / 2f + mFAB.getWidth() / 2f + dpToPx(16) ) * slideOffset);
mFAB.setTranslationX(translationX);
float imgWidth = Math.max(getResources().getDimension(R.dimen.abc_action_bar_default_height_material), width * slideOffset);
Log.e("test", "onSlide: max " + imgWidth);
Log.e("test", "onSlide: img" + String.valueOf(mAlbumArt.getLayoutParams().height));
mAlbumArt.getLayoutParams().height = (int) (imgWidth);
mAlbumArt.getLayoutParams().width = (int) (imgWidth);
mAlbumArt.requestLayout();
// tried this, but does nothing: mAlbumArt.invalidate();
}
});
I can see the Logs and they say that the size is changing, but the image stays small (third image). The weirdest thing, is that when I press play/pause the image does resize (only when the bottom sheet is fully extended or not extended at all, can't do that when scrolling). The play/pause is handled by the SDK and I only call this function:
private void updateButtons() {
boolean loggedIn = isLoggedIn();
mFAB.setImageResource(mYfitopsHelper.isPlaying() ? android.R.drawable.ic_media_pause : android.R.drawable.ic_media_play);
// Login button should be the inverse of the logged in state
Button loginButton = (Button) findViewById(R.id.login_button);
loginButton.setText(loggedIn ? R.string.logout_button_label : R.string.login_button_label);
// Set enabled for all widgets which depend on initialized state
for (int id : REQUIRES_INITIALIZED_STATE) {
findViewById(id).setEnabled(loggedIn);
}
// Same goes for the playing state
boolean playing = loggedIn && mYfitopsHelper.mCurrentPlayerState.trackUri != null;
for (int id : REQUIRES_PLAYING_STATE) {
findViewById(id).setEnabled(playing);
}
}
I don't understand why the Imageview is only updated when one of the buttons in pressed... Any suggestions?
Related
My goal is to have a sliding view on fragment 1 which slides a set distance before fragment 2 is pulled into visual view.
I made a visual here that shows what I am trying to do:
Pic 1 Shows the default view of fragment 1 (pink) and sliding view (green), nothing has happened in this state.
Pic 2 The sliding view is able to slide to it's cutoff (black line). It cannot move past this on this fragment.
Pic 3 The entire fragment 1 begins to slide, fragment 2 (blue) is now starting to be pulled into the view
Pic 4 Both fragments continue to slide as before.
Pic 5 Fragment 2 has now slid into view.
When the user tries to return to the previous screen the transition happens in reverse.
Right now I have implemented a ViewPager which hosts the fragments 1 and 2. The default ViewPager behavior allows me to slide between the two fragments, but I am unsure how to implement the functionality I have described.
I was able to achieve the desired transition with the use of a ViewPager PageTransformer
I am using Xamarin.Android in my project, so C# is used. Although this snippet is in C# it could easily be ported to Java code for use in other projects.
float passpoint = 7/8f;
public void TransformPage(View view, float position)
{
var pageWidth = view.Width;
var slidingGap = pageWidth - pageWidth * passpoint;
var translation = position * pageWidth;
if (position < -1)
{ // [-Infinity,-1)
// This page is way off-screen to the left.
view.Alpha = 0;
}
else if (position <= 1)
{ // [-1,1]
var r = new Rect();
//if valid, this view is the startpage
Button button = (Button)view.FindViewById(Resource.Id.slidingButton);
if (button != null)
{
var posPassPoint = -(1 - passpoint);
float ratio;
//create sliding gap
if (position > posPassPoint)
{
ratio = RatioBetween(position, posPassPoint, 0);
button.TranslationX = slidingGap * -ratio;
}
//compensate for sliding gap after it has been reached
else
{
//1- bc want the ratio to shrink
ratio = 1-RatioBetween(position, -1, posPassPoint);
}
view.TranslationX = slidingGap * ratio;
}
//valid on views other than start page (no sliding button)
if (button == null)
{
float ratio;
//create sliding gap
if (position >= passpoint)
ratio = RatioBetween(position, passpoint, 1);
//compensate for sliding gap after it has been reached
else
ratio = 1 - RatioBetween(position, 0, passpoint);
view.TranslationX = slidingGap * ratio;
}
}
else
{ // (1,+Infinity]
// This page is way off-screen to the right.
view.Alpha = 0;
}
}
float RatioBetween(float input, float upperBound, float lowerBound)
{
return (input - lowerBound) / (upperBound - lowerBound);
}
I would like to smoothly animate some views Y position based on the user scrolling a bottom sheet.
http://imgur.com/dVBlh83
However the animation is slightly jerky.
This is probably because scrolling a bottomsheet gives me a slideOffset at some interval, which is too infrequent to setTranlationY like I'm currently doing.
Is there a better way to do this.
Bottom sheet callback
BottomSheetBehavior.BottomSheetCallback() {
#Override public void onSlide(#NonNull View bottomSheetView, float slideOffset) {
int offset = (int) (slideOffset * 100);
if (offset > 0) {
scrollingHeaderView.animate(offset);
}
}
});
scrollingHeaderView
public void animate(int offset) {
position = -(offset / 2);
arrow.setTranslationY(offset * ANIMATION_RATIO_ARROW);
detailView.setTranslationY(offset * ANIMATION_RATIO);
}
I have tried using ObjectAnimator to create lots of small animations at each step.
ObjectAnimator.ofFloat(arrow, "translationY", arrow.getTranslationY(), position * ANIMATION_RATIO_ARROW).start();
in case it helps anyone else. I got it looking much better.
First i set the ration by pixel * range of values i'd get. So offset will be 0 -> 100. Mutliplied by the dp i want the view to move
private static int ANIMATION_RANGE = 100;
arrowScale = -(getResources().getDimensionPixelOffset(R.dimen.arrow_distance) / ANIMATION_RANGE);
containerScale = -(getResources().getDimensionPixelOffset(R.dimen.detail_distance) / ANIMATION_RANGE);
then as the value changes i set this using translationY
arrow.setTranslationY(position * arrowScale);
detailView.setTranslationY(position * containerScale);
I also needed a reset method, which animate back to the original position
public void reset() {
arrow.animate().setDuration(100).translationY(0);
detailView.animate().setDuration(100).translationY(0);
}
The latest Facebook's android app has a very nice floating comment window. There the user can dismiss the window swiping up or down making it really ease to use.
I want to implement a similar behaviour in my app but I don't know how to do it. Any idea or clue about how to do it will be really appreciated.
Screenshots of the Facebook app
(sorry, the Facebook app from where I took the screenshots is in Japanese)
I write some code that match this closing/resizing behaviour, I don't know if it's the way to go but my code is based on Activity class. First thing I do is create an activity and give it Transluscenttheme to get an activity with transparent background.
In my manifest.xml :
<activity
android:name=".PopupActivity"
android:label="#string/title_activity_popup"
<!-- Use Translucent theme to get transparent activity background
and NoTitleBar to avoid super old style title bar ;) -->
android:theme="#android:style/Theme.Translucent.NoTitleBar">
</activity>
Then I create a simple layout file containing a textview (corresponding to Facebook tchatting part) and a view (corresponding to Facebook "Write your msg"/"send smiley" tab)
my layout/activity_popup.xml :
<RelativeLayout
xmlns:android="http://schemas.android.com/apk/res/android"
android:id="#+id/base_popup_layout"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:padding="8dp"
android:background="#android:color/darker_gray"
android:layout_marginBottom="124dp">
<TextView
android:text="#string/hello_world"
android:layout_width="match_parent"
android:layout_height="200dp"
android:background="#android:color/black"/>
<View
android:layout_width="match_parent"
android:layout_height="80dp"
android:layout_alignParentBottom="true"
android:background="#android:color/holo_blue_dark"/>
</RelativeLayout>
Finally I handle touch and move event in my PopupActivity class, I use onTouchListener which provide callback in onTouch method.
PopupActivity
public class PopupActivity extends Activity implements View.OnTouchListener{
private RelativeLayout baseLayout;
private int previousFingerPosition = 0;
private int baseLayoutPosition = 0;
private int defaultViewHeight;
private boolean isClosing = false;
private boolean isScrollingUp = false;
private boolean isScrollingDown = false;
#Override
protected void onCreate(Bundle savedInstanceState){
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_popup);
baseLayout = (RelativeLayout) findViewById(R.id.base_popup_layout);
baseLayout.setOnTouchListener(this);
}
public boolean onTouch(View view, MotionEvent event) {
// Get finger position on screen
final int Y = (int) event.getRawY();
// Switch on motion event type
switch (event.getAction() & MotionEvent.ACTION_MASK) {
case MotionEvent.ACTION_DOWN:
// save default base layout height
defaultViewHeight = baseLayout.getHeight();
// Init finger and view position
previousFingerPosition = Y;
baseLayoutPosition = (int) baseLayout.getY();
break;
case MotionEvent.ACTION_UP:
// If user was doing a scroll up
if(isScrollingUp){
// Reset baselayout position
baseLayout.setY(0);
// We are not in scrolling up mode anymore
isScrollingUp = false;
}
// If user was doing a scroll down
if(isScrollingDown){
// Reset baselayout position
baseLayout.setY(0);
// Reset base layout size
baseLayout.getLayoutParams().height = defaultViewHeight;
baseLayout.requestLayout();
// We are not in scrolling down mode anymore
isScrollingDown = false;
}
break;
case MotionEvent.ACTION_MOVE:
if(!isClosing){
int currentYPosition = (int) baseLayout.getY();
// If we scroll up
if(previousFingerPosition >Y){
// First time android rise an event for "up" move
if(!isScrollingUp){
isScrollingUp = true;
}
// Has user scroll down before -> view is smaller than it's default size -> resize it instead of change it position
if(baseLayout.getHeight()<defaultViewHeight){
baseLayout.getLayoutParams().height = baseLayout.getHeight() - (Y - previousFingerPosition);
baseLayout.requestLayout();
}
else {
// Has user scroll enough to "auto close" popup ?
if ((baseLayoutPosition - currentYPosition) > defaultViewHeight / 4) {
closeUpAndDismissDialog(currentYPosition);
return true;
}
//
}
baseLayout.setY(baseLayout.getY() + (Y - previousFingerPosition));
}
// If we scroll down
else{
// First time android rise an event for "down" move
if(!isScrollingDown){
isScrollingDown = true;
}
// Has user scroll enough to "auto close" popup ?
if (Math.abs(baseLayoutPosition - currentYPosition) > defaultViewHeight / 2)
{
closeDownAndDismissDialog(currentYPosition);
return true;
}
// Change base layout size and position (must change position because view anchor is top left corner)
baseLayout.setY(baseLayout.getY() + (Y - previousFingerPosition));
baseLayout.getLayoutParams().height = baseLayout.getHeight() - (Y - previousFingerPosition);
baseLayout.requestLayout();
}
// Update position
previousFingerPosition = Y;
}
break;
}
return true;
}
}
There are two small methods called when user has scroll enough to close popup (ie animate and finish activity) :
public void closeUpAndDismissDialog(int currentPosition){
isClosing = true;
ObjectAnimator positionAnimator = ObjectAnimator.ofFloat(baseLayout, "y", currentPosition, -baseLayout.getHeight());
positionAnimator.setDuration(300);
positionAnimator.addListener(new Animator.AnimatorListener()
{
. . .
#Override
public void onAnimationEnd(Animator animator)
{
finish();
}
. . .
});
positionAnimator.start();
}
public void closeDownAndDismissDialog(int currentPosition){
isClosing = true;
Display display = getWindowManager().getDefaultDisplay();
Point size = new Point();
display.getSize(size);
int screenHeight = size.y;
ObjectAnimator positionAnimator = ObjectAnimator.ofFloat(baseLayout, "y", currentPosition, screenHeight+baseLayout.getHeight());
positionAnimator.setDuration(300);
positionAnimator.addListener(new Animator.AnimatorListener()
{
. . .
#Override
public void onAnimationEnd(Animator animator)
{
finish();
}
. . .
});
positionAnimator.start();
}
With all this code your should be able to start PopupActivity that globally match Facebook popup behaviour. It's just a draft class and a lot of work remains to do : add animations, work on closing parameters and so on...
Screenshots :
I think you can use BottomSheetDialogFragment component from appcompat libarary. Check this article for the information:
https://medium.com/#nullthemall/new-bottomsheet-caab21aff19b#.gpu1l516z
You could also get useful information from
documentaion.
Well, the OP title was asking for a floating activity, but the OP content was looking for a floating comment window that is like facebook comment window.
So, here this will be implemented by a DialogFragment which provide us with the behavior of automatically bouncing the dialog to its original state/window size whenever you swipe up or down. And this behavior is kept when the dialog is swiped a little (exactly swiped a distance less than the half of the original layout size).
The remaining thing is it dismiss this dialog if its size is less than the half of the original window size; in other words, if it's swiped greater than the half of the original layout size. This part is adjusted from the accepted answer by changing the only the Y position of the root layout of the dialog window, and without changing the size of the window as this provided weird resizing behavior.
First create this style to have a transparent background to the dialog window:
<style name="NoBackgroundDialogTheme" parent="Theme.AppCompat.Light.Dialog">
<item name="android:windowBackground">#null</item>
</style>
This style will be applied in the DialogFragment by overriding getTheme() method.
And here is the customized DialogFragment:
class MyDialogFragment : DialogFragment(), View.OnTouchListener {
private var rootLayoutY: Int = 0
private val rootLayout by lazy {
requireView().findViewById<ConstraintLayout>(R.id.dialog_root)
}
private var oldY = 0
private var baseLayoutPosition = 0
private var defaultViewHeight = 0
private var isScrollingUp = false
private var isScrollingDown = false
override fun getTheme(): Int {
return R.style.NoBackgroundDialogTheme
}
override fun onCreateView(
inflater: LayoutInflater,
container: ViewGroup?,
savedInstanceState: Bundle?
): View {
val view: View = inflater.inflate(
R.layout.fragment_dialog_facebook_comment, container,
false
)
view.setBackgroundResource(R.drawable.rounded_background)
return view
}
override fun onStart() {
super.onStart()
// Making the dialog full screen
dialog?.window?.setLayout(
LinearLayout.LayoutParams.MATCH_PARENT,
LinearLayout.LayoutParams.MATCH_PARENT
)
}
#SuppressLint("ClickableViewAccessibility")
override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
super.onViewCreated(view, savedInstanceState)
rootLayout.setOnTouchListener(this)
rootLayout.viewTreeObserver.addOnGlobalLayoutListener(object :
OnGlobalLayoutListener {
override fun onGlobalLayout() {
rootLayout.viewTreeObserver
.removeOnGlobalLayoutListener(this)
// save default base layout height
defaultViewHeight = rootLayout.height
}
})
}
#SuppressLint("ClickableViewAccessibility")
override fun onTouch(v: View?, event: MotionEvent?): Boolean {
// Get finger position on screen
val y = event!!.rawY.toInt()
// Switch on motion event type
when (event.action and MotionEvent.ACTION_MASK) {
MotionEvent.ACTION_DOWN -> {
// Init finger and view position
oldY = y
baseLayoutPosition = rootLayout.y.toInt()
}
MotionEvent.ACTION_UP -> {
if (rootLayoutY >= defaultViewHeight / 2) {
dismiss()
return true
}
// If user was doing a scroll up
if (isScrollingUp) {
// Reset baselayout position
rootLayout.y = 0f
// We are not in scrolling up mode anymore
isScrollingUp = false
}
// If user was doing a scroll down
if (isScrollingDown) {
// Reset baselayout position
rootLayout.y = 0f
// Reset base layout size
rootLayout.layoutParams.height = defaultViewHeight
rootLayout.requestLayout()
// We are not in scrolling down mode anymore
isScrollingDown = false
}
}
MotionEvent.ACTION_MOVE -> {
rootLayoutY = abs(rootLayout.y.toInt())
// Change base layout size and position (must change position because view anchor is top left corner)
rootLayout.y = rootLayout.y + (y - oldY)
if (oldY > y) { // scrolling up
if (!isScrollingUp) isScrollingUp = true
} else { // Scrolling down
if (!isScrollingDown) isScrollingDown = true
}
// Update y position
oldY = y
}
}
return true
}
}
In my case, the root layout of the dialog is a ConstraintLayout.
I am doing auto-horizontal scrolling. So i have 15 items. Now i want to access at 12 item so my index is 11. But i am unable to scroll it auto when a index occur.
horizontalScrollView.scrollTo(12, 0);
#Override
public void onPageSelected(int page) {
for(int i = 0; i < holeTitle.length; i++) {
if(i == page) {
title[i].setTextColor(0xffffffff);
horizontalScrollView.scrollTo(12, 0);
}
else {
title[i].setTextColor(0xffe0e0e0);
}
}
}
please expert make a look.
DmRomantsov's answer is the right way to scroll to the 12th button. However, getLeft() and getRight() methods return 0 because the layout is not displayed yet on the screen. It is too early to calculate the width of the layout parent and children. To achieve it, you need to do your auto-scroll inside onWindowFocusChanged.
#Override
public void onWindowFocusChanged(boolean hasFocus){
super.onWindowFocusChanged(hasFocus);
if(hasFocus){
// do smoothScrollTo(...);
}
}
However, inside a Fragment, this method above will not work. I just wrote it to give a clue, to understand the concept. To have the same behaviour in Fragment, you just need to do a Runnable which lets the time to your UI to be displayed. Then, do this with a LinearLayout oriented to horizontal:
// Init variables
HorizontalScrollView mHS;
LinearLayout mLL;
// onCreateView method
#Override
public View onCreateView(LayoutInflater inflater, ViewGroup container,
Bundle savedInstanceState) {
View view = inflater.inflate(R.layout.layout_container, container, false);
// Find your views
mHS = (HorizontalScrollView)view.findViewById(R.id.hscrollview);
mLL = (LinearLayout)view.findViewById(R.id.hscrollview_container);
// Do a Runnable on the inflated view
view.post(new Runnable() {
#Override
public void run() {
Log.v("","Left position of 12th child = "+mLL.getChildAt(11).getLeft());
mHS.smoothScrollTo(mLL.getChildAt(11).getLeft(), 0);
}
});
return view;
}
Middle HorizontalScrollView:
Your question was to auto-scroll until your 12th child. However, in the comments below, you ask me to auto-scroll at the middle of the HorizontalScrollView, I assume on every device. You need to calculate the width of the screen, the total width of the container and how many children are displayed inside the device width. Here is a simple code:
// Auto scroll to the middle (regardless of the width screen)
view.post(new Runnable() {
#Override
public void run() {
// Width of the screen
DisplayMetrics metrics = getActivity().getResources()
.getDisplayMetrics();
int widthScreen = metrics.widthPixels;
Log.v("","Width screen total = " + widthScreen);
// Width of the container (LinearLayout)
int widthContainer = mLL.getWidth();
Log.v("","Width container total = " + widthContainer );
// Width of one child (Button)
int widthChild = mLL.getChildAt(0).getWidth();
Log.v("","Width child = " + widthChild);
// Nb children in screen
int nbChildInScreen = widthScreen / widthChild;
Log.v("","Width screen total / Width child = " + nbChildInScreen);
// Width total of the space outside the screen / 2 (= left position)
int positionLeftWidth = (widthContainer
- (widthChild * nbChildInScreen))/2;
Log.v("","Position left to the middle = " + positionLeftWidth);
// Auto scroll to the middle
mHS.smoothScrollTo(positionLeftWidth, 0);
}
});
/**
* Your value might be resumed by:
*
* int positionLeftWidth =
* ( mLL.getWidth() - ( mLL.getChildAt(0).getWidth() *
* ( metrics.widthPixels / mLL.getChildAt(0).getWidth() ) ) ) / 2;
*
**/
Middle HorizontalScrollView with chosen Value:
I have a bit misunderstand the real request. Actually, you wanted to auto-scroll until a chosen child view, and display this view at the middle of the screen.
Then, I changed the last int positionLeftWidth which refers now to the left position of the chosen view relative to its parent, the number of children contained in one screen, and the half width of the chosen view. So, the code is the same as above, except positionLeftWidth:
// For example the chosen value is 7
// 7th Child position left
int positionChildAt = mLL.getChildAt(6).getLeft();
// Width total of the auto-scroll (positionLeftWidth)
int positionLeftWidth = positionChildAt - // position 7th child from left less
( ( nbChildInScreen // ( how many child contained in screen
* widthChild ) / 2 ) // multiplied by their width ) divide by 2
+ ( widthChild / 2 ); // plus ( the child view divide by 2 )
// Auto-scroll to the 7th child
mHS.smoothScrollTo(positionLeftWidth, 0);
Then, whatever the value in getChildAt() method, and whatever the width screen, you will always have the chosen (in your case) button at the middle of the screen.
Try
horizontalScrollView.smoothScrollTo(horizontalScrollView.getChildAt(11).getRight(),0);
first patameter - X coord, second - Y.
Offset:
public final void smoothScrollBy (int dx, int dy)
Absolute:
public final void smoothScrollTo (int x, int y)
Try horizontalScrollView.smoothScrollBy(12, 0);
Try this is working code. position will be where you want to scroll
final HorizontalScrollView mHorizontalScrollView = (HorizontalScrollView) .findViewById(R.id.horizontalScrollView);
mHorizontalScrollView.postDelayed(new Runnable() {
#Override
public void run() {
mHorizontalScrollView.scrollTo(position, 0);
mHorizontalScrollView.smoothScrollBy(1200, 0);
}
},100);
What's the best way to check if the view is visible on the window?
I have a CustomView which is part of my SDK and anybody can add CustomView to their layouts. My CustomView is taking some actions when it is visible to the user periodically. So if view becomes invisible to the user then it needs to stop the timer and when it becomes visible again it should restart its course.
But unfortunately there is no certain way of checking if my CustomView becomes visible or invisible to the user. There are few things that I can check and listen to: onVisibilityChange //it is for view's visibility change, and is introduced in new API 8 version so has backward compatibility issue
onWindowVisibilityChange //but my CustomView can be part of a ViewFlipper's Views so it can pose issues
onDetachedFromWindows //this not as useful
onWindowFocusChanged //Again my CustomView can be part of ViewFlipper's views. So if anybody has faced this kind of issues please throw some light.
In my case the following code works the best to listen if the View is visible or not:
#Override
protected void onWindowVisibilityChanged(int visibility) {
super.onWindowVisibilityChanged(visibility);
Log.e(TAG, "is view visible?: " + (visibility == View.VISIBLE));
}
onDraw() is called each time the view needs to be drawn. When the view is off screen then onDraw() is never called. When a tiny bit of the view is becomes visible to the user then onDraw() is called. This is not ideal but I cannot see another call to use as I want to do the same thing. Remember to call the super.onDraw or the view won't get drawn. Be careful of changing anything in onDraw that causes the view to be invalidate as that will cause another call to onDraw.
If you are using a listview then getView can be used whenever your listview becomes shown to the user.
obviously the activity onPause() is called all your views are all covered up and are not visible to the user. perhaps calling invalidate() on the parent and if ondraw() is not called then it is not visible.
This is a method that I have used quite a bit in my apps and have had work out quite well for me:
static private int screenW = 0, screenH = 0;
#SuppressWarnings("deprecation") static public boolean onScreen(View view) {
int coordinates[] = { -1, -1 };
view.getLocationOnScreen(coordinates);
// Check if view is outside left or top
if (coordinates[0] + view.getWidth() < 0) return false;
if (coordinates[1] + view.getHeight() < 0) return false;
// Lazy get screen size. Only the first time.
if (screenW == 0 || screenH == 0) {
if (MyApplication.getSharedContext() == null) return false;
Display display = ((WindowManager)MyApplication.getSharedContext().getSystemService(Context.WINDOW_SERVICE)).getDefaultDisplay();
try {
Point screenSize = new Point();
display.getSize(screenSize); // Only available on API 13+
screenW = screenSize.x;
screenH = screenSize.y;
} catch (NoSuchMethodError e) { // The backup methods will only be used if the device is running pre-13, so it's fine that they were deprecated in API 13, thus the suppress warnings annotation at the start of the method.
screenW = display.getWidth();
screenH = display.getHeight();
}
}
// Check if view is outside right and bottom
if (coordinates[0] > screenW) return false;
if (coordinates[1] > screenH) return false;
// Else, view is (at least partially) in the screen bounds
return true;
}
To use it, just pass in any view or subclass of view (IE, just about anything that draws on screen in Android.) It'll return true if it's on screen or false if it's not... pretty intuitive, I think.
If you're not using the above method as a static, then you can probably get a context some other way, but in order to get the Application context from a static method, you need to do these two things:
1 - Add the following attribute to your application tag in your manifest:
android:name="com.package.MyApplication"
2 - Add in a class that extends Application, like so:
public class MyApplication extends Application {
// MyApplication exists solely to provide a context accessible from static methods.
private static Context context;
#Override public void onCreate() {
super.onCreate();
MyApplication.context = getApplicationContext();
}
public static Context getSharedContext() {
return MyApplication.context;
}
}
In addition to the view.getVisibility() there is view.isShown().
isShown checks the view tree to determine if all ancestors are also visible.
Although, this doesn't handle obstructed views, only views that are hidden or gone in either themselves or one of its parents.
In dealing with a similar issue, where I needed to know if the view has some other window on top of it, I used this in my custom View:
#Override
public void onWindowFocusChanged(boolean hasWindowFocus) {
super.onWindowFocusChanged(hasWindowFocus);
if (!hasWindowFocus) {
} else {
}
}
This can be checked using getGlobalVisibleRect method. If rectangle returned by this method has exactly the same size as View has, then current View is completely visible on the Screen.
/**
* Returns whether this View is completely visible on the screen
*
* #param view view to check
* #return True if this view is completely visible on the screen, or false otherwise.
*/
public static boolean onScreen(#NonNull View view) {
Rect visibleRect = new Rect();
view.getGlobalVisibleRect(visibleRect);
return visibleRect.height() == view.getHeight() && visibleRect.width() == view.getWidth();
}
If you need to calculate visibility percentage you can do it using square calculation:
float visiblePercentage = (visibleRect.height() * visibleRect.width()) / (float)(view.getHeight() * view.getWidth())
This solution takes into account view obstructed by statusbar and toolbar, also as view outside the window (e.g. scrolled out of screen)
/**
* Test, if given {#code view} is FULLY visible in window. Takes into accout window decorations
* (statusbar and toolbar)
*
* #param view
* #return true, only if the WHOLE view is visible in window
*/
public static boolean isViewFullyVisible(View view) {
if (view == null || !view.isShown())
return false;
//windowRect - will hold available area where content remain visible to users
//Takes into account screen decorations (e.g. statusbar)
Rect windowRect = new Rect();
view.getWindowVisibleDisplayFrame(windowRect);
//if there is toolBar, get his height
int actionBarHeight = 0;
Context context = view.getContext();
if (context instanceof AppCompatActivity && ((AppCompatActivity) context).getSupportActionBar() != null)
actionBarHeight = ((AppCompatActivity) context).getSupportActionBar().getHeight();
else if (context instanceof Activity && ((Activity) context).getActionBar() != null)
actionBarHeight = ((Activity) context).getActionBar().getHeight();
//windowAvailableRect - takes into account toolbar height and statusbar height
Rect windowAvailableRect = new Rect(windowRect.left, windowRect.top + actionBarHeight, windowRect.right, windowRect.bottom);
//viewRect - holds position of the view in window
//(methods as getGlobalVisibleRect, getHitRect, getDrawingRect can return different result,
// when partialy visible)
Rect viewRect;
final int[] viewsLocationInWindow = new int[2];
view.getLocationInWindow(viewsLocationInWindow);
int viewLeft = viewsLocationInWindow[0];
int viewTop = viewsLocationInWindow[1];
int viewRight = viewLeft + view.getWidth();
int viewBottom = viewTop + view.getHeight();
viewRect = new Rect(viewLeft, viewTop, viewRight, viewBottom);
//return true, only if the WHOLE view is visible in window
return windowAvailableRect.contains(viewRect);
}
you can add to your CustomView's constractor a an onScrollChangedListener from ViewTreeObserver
so if your View is scrolled of screen you can call view.getLocalVisibleRect() and determine if your view is partly offscreen ...
you can take a look to the code of my library : PercentVisibleLayout
Hope it helps!
in your custom view, set the listeners:
getViewTreeObserver().addOnScrollChangedListener(this);
getViewTreeObserver().addOnGlobalLayoutListener(this);
I am using this code to animate a view once when it is visible to user.
2 cases should be considered.
Your view is not in the screen. But it will be visible if user scrolled it
public void onScrollChanged() {
final int i[] = new int[2];
this.getLocationOnScreen(i);
if (i[1] <= mScreenHeight - 50) {
this.post(new Runnable() {
#Override
public void run() {
Log.d("ITEM", "animate");
//animate once
showValues();
}
});
getViewTreeObserver().removeOnScrollChangedListener(this);
getViewTreeObserver().removeOnGlobalLayoutListener(this);
}
}
Your view is initially in screen.(Not in somewhere else invisible to user in scrollview, it is in initially on screen and visible to user)
public void onGlobalLayout() {
final int i[] = new int[2];
this.getLocationOnScreen(i);
if (i[1] <= mScreenHeight) {
this.post(new Runnable() {
#Override
public void run() {
Log.d("ITEM", "animate");
//animate once
showValues();
}
});
getViewTreeObserver().removeOnGlobalLayoutListener(this);
getViewTreeObserver().removeOnScrollChangedListener(this);
}
}