For an app I need to develop, I need to be able to select one view from ViewGroup when user long press on the view have to popup an alert dialog, from that i will select options depending on the requirement, those are delete view and change background color for that particular view. I am creating different custom view's ( Custom view extends from View) and It has Touchlistener's. Dynamically attaching those view's to layout (RelativeLayout). Let us assume my layout having 3 views.
My questions:
I want to select one view from the list of available view's on the layout.
How to restrict the Custom view size instead of occupying full parent view.
I am facing Problem's like this: Look at this image if i set background color for custom view it look like this, Actually i was set for layout background color White here custom view occupies full screen. so, unable to see parent background color. Here problem with customview size, unable to restrict the customview size rather than occupying full parent view. even if i touch outside of my custom view, the touch listeners of custom view was triggering. i want to restrict those touch listeners upto my customview only..
.
Main Requirement:
Finally i want to be achieve like this below image, Now i am able to add more custom view's to layout,but unable to delete selected one. When i long press on any one of the view from the layout, it should popup one alert dialog with two buttons, if i select one selected view should be delete for another selection background color change.
RelaventCode: i am adding view to layout like this.
findViewById(R.id.line).setOnClickListener(new OnClickListener() {
#Override
public void onClick(View v) {
CustomView drawView = new CustomView(context);
frame.addView(drawView);
}
});
and this is the customview :
public class CustomView extends View {
public CustomView (Context context) {
super(context);
init();
}
private void init() {
Paint paint = new Paint();
Paint pai = new Paint();
Path path = new Path();
paint.setColor(Color.RED);
paint.setStyle(Paint.Style.STROKE);
paint.setStrokeWidth(5);
path.lineTo(50, 100);
}
#Override
public void onDraw(Canvas canvas) {
path.setFillType(Path.FillType.EVEN_ODD);
path.moveTo(Top_Point.x, Top_Point.y);
path.lineTo(Right_Point.x, Right_Point.y);
path.lineTo(Left_Point.x, Left_Point.y);
path.lineTo(Top_Point.x, Top_Point.y);
path.close();
canvas.drawPath(path, Inner_Paint);
canvas.drawLine(Top_Point.x, Top_Point.y, Right_Point.x, Right_Point.y,
Lines_Paint);
canvas.drawLine(Right_Point.x, Right_Point.y, Left_Point.x,
Left_Point.y, Lines_Paint);
canvas.drawLine(Left_Point.x, Left_Point.y, Top_Point.x, Top_Point.y,
Lines_Paint);
}
#Override
public boolean onTouchEvent(MotionEvent event) {
switch (event.getAction()) {
case MotionEvent.ACTION_DOWN:
PointF NewP = new PointF(0, 0);
NewP.x = event.getX();
NewP.y = event.getY();
if (TopTouchArea.contains(event.getX(), event.getY())) {
currentTouch = TOUCH_TOP;
} else if (RightTouchArea.contains(event.getX(), event.getY())) {
currentTouch = TOUCH_RIGHT;
} else if (LeftTouchArea.contains(event.getX(), event.getY())) {
currentTouch = TOUCH_LEFT;
} else if (a) {
Translate_Point.x = event.getX();
Translate_Point.y = event.getY();
currentTouch = Inner_Touch;
return true; // Return false if user touches none of the
// corners
} else {
return false;
}
return true;
case MotionEvent.ACTION_MOVE:
switch (currentTouch) {
case TOUCH_TOP:
/* ------ */
case TOUCH_RIGHT:
/* ----- */
invalidate();
return true;
case TOUCH_LEFT:
/* ------ */
invalidate();
return true;
case Inner_Touch:
/* ------ */
invalidate();
return true;
}
return false;
case MotionEvent.ACTION_UP:
switch (currentTouch) {
case TOUCH_TOP:
/* ----- */
return true;
case TOUCH_RIGHT:
/* ----- */
return true;
case TOUCH_LEFT:
/* ----- */
return true;
case Inner_Touch:
/* ----- */
invalidate();
return true;
}
return false;
}
return super.onTouchEvent(event);
}
You should override onSizeChanged or onMeasure methods to let your ViewGroup know your view's size. And make sure that your view's width and height are both WRAP_CONTENT.
About touch event - problem here is that your view size is matching parent size, so it intercepts all touch events (since it returns true in onTouchEvent).
And here is a great tutorial
Add an OnLongClickListener to the customview before adding it to the view group.
To help identify the view at a higher level, you may want to set an identifier on the view using setTag("id", id);
If I understand you correctly, the view you add is too big and fills it's parent.
Isn't it sufficient to do something like
TwoPointsDraw drawView = new TwoPointsDraw(context);
FrameLayout.LayoutParams lp = new FrameLayout.LayoutParams(
LayoutParams.WRAP_CONTENT,
LayoutParams.WRAP_CONTENT);
drawView.setLayoutParams(lp)
frame.addView(drawView);
Checkout this project, it might be helpful => Android multitouch controller, a simple multitouch pinch zoom library for Android.
Here is another example.
Related
I need to implement a ListView with triangular shaped items as shown in this image. The views that one adds to a ListView generally are rectangular in shape. Even in the documentation, a View is described as "occupies a rectangular area on the screen and is responsible for drawing and event handling".
How can I add non-rectangular shapes to the ListView and at the same time making sure that the click area is restricted to the shape, in this case a triangle.
Thank you!
My solution would use overlapping Views that are cropped to alternating triangles and only accept touch events within its triangle.
The issue is that the ListView does not really support overlapping item Views, therefore my example just loads all items at once into a ScrollView, which may be bad if you have more than, say, 30 items. Maybe this is doable with a RecyclerView but I haven't looked into that.
I have chosen to extend the FrameLayout to implement the triangle View logic, so you can use it as the root View of a list item and put anything you want in it:
public class TriangleFrameLayout extends FrameLayout {
// TODO: constructors
public enum Align { LEFT, RIGHT };
private Align alignment = Align.LEFT;
/**
* Specify whether it's a left or a right triangle.
*/
public void setTriangleAlignment(Align alignment) {
this.alignment = alignment;
}
#Override
public void draw(Canvas canvas) {
// crop drawing to the triangle shape
Path mask = new Path();
Point[] tria = getTriangle();
mask.moveTo(tria[0].x, tria[0].y);
mask.lineTo(tria[1].x, tria[1].y);
mask.lineTo(tria[2].x, tria[2].y);
mask.close();
canvas.save();
canvas.clipPath(mask);
super.draw(canvas);
canvas.restore();
}
#Override
public boolean onTouchEvent(MotionEvent event) {
// check if touch event is within the triangle shape
if (event.getActionMasked() == MotionEvent.ACTION_DOWN) {
Point touch = new Point((int) event.getX(), (int) event.getY());
Point[] tria = getTriangle();
if (!isPointInsideTrigon(touch, tria[0], tria[1], tria[2])) {
// ignore touch event outside triangle
return false;
}
}
return super.onTouchEvent(event);
}
private boolean isPointInsideTrigon(Point s, Point a, Point b, Point c) {
// stolen from http://stackoverflow.com/a/9755252
int as_x = s.x - a.x;
int as_y = s.y - a.y;
boolean s_ab = (b.x - a.x) * as_y - (b.y - a.y) * as_x > 0;
if ((c.x - a.x) * as_y - (c.y - a.y) * as_x > 0 == s_ab)
return false;
if ((c.x - b.x) * (s.y - b.y) - (c.y - b.y) * (s.x - b.x) > 0 != s_ab)
return false;
return true;
}
private Point[] getTriangle() {
// define the triangle shape of this View
boolean left = alignment == Align.LEFT;
Point a = new Point(left ? 0 : getWidth(), -1);
Point b = new Point(left ? 0 : getWidth(), getHeight() + 1);
Point c = new Point(left ? getWidth() : 0, getHeight() / 2);
return new Point[] { a, b, c };
}
}
An example item XML layout, with the TriangleFrameLayout as root, could look like this:
<?xml version="1.0" encoding="utf-8"?>
<your.package.TriangleFrameLayout
xmlns:android="http://schemas.android.com/apk/res/android"
android:id="#+id/root_triangle"
android:layout_width="match_parent"
android:layout_height="160dp"
android:layout_marginTop="-80dp"
android:clickable="true"
android:foreground="?attr/selectableItemBackground">
<TextView
android:id="#+id/item_text"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_gravity="center"
android:padding="20dp"
android:textSize="30dp"
android:textStyle="bold"
android:textColor="#ffffff" />
</your.package.TriangleFrameLayout>
Here we have a fixed height of 160dp that you can change to whatever you want. The important thing is the negative top margin of half the height, -80dp in this case, that causes the items to overlap and the different triangles to match up.
Now we can inflate multiple such items and add it to a list, i.e. ScrollView. This shows an example layout for our Activity or Framgent:
<?xml version="1.0" encoding="utf-8"?>
<ScrollView xmlns:android="http://schemas.android.com/apk/res/android"
android:layout_width="match_parent"
android:layout_height="match_parent">
<LinearLayout
android:id="#+id/list"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:orientation="vertical">
</LinearLayout>
</ScrollView>
And the code to populate the list:
Here I created a dummy Adapter, analog to a ListView, that just enumerates our items from 0 to 15.
ListAdapter adapter = new BaseAdapter() {
#Override
public int getCount() { return 16; }
#Override
public Integer getItem(int position) { return position; }
#Override
public long getItemId(int position) { return position; }
#Override
public View getView(int position, View view, ViewGroup parent) {
if (view == null) {
view = getLayoutInflater().inflate(R.layout.item_tria, parent, false);
}
// determine whether it's a left or a right triangle
TriangleFrameLayout.Align align =
(position & 1) == 0 ? TriangleFrameLayout.Align.LEFT : TriangleFrameLayout.Align.RIGHT;
// setup the triangle
TriangleFrameLayout triangleFrameLayout = (TriangleFrameLayout) view.findViewById(R.id.root_triangle);
triangleFrameLayout.setTriangleAlignment(align);
triangleFrameLayout.setBackgroundColor(Color.argb(255, 0, (int) (Math.random() * 256), (int) (Math.random() * 256)));
// setup the example TextView
TextView textView = (TextView) view.findViewById(R.id.item_text);
textView.setText(getItem(position).toString());
textView.setGravity((position & 1) == 0 ? Gravity.LEFT : Gravity.RIGHT);
return view;
}
};
// populate the list
LinearLayout list = (LinearLayout) findViewById(R.id.list);
for (int i = 0; i < adapter.getCount(); ++i) {
final int position = i;
// generate the item View
View item = adapter.getView(position, null, list);
list.addView(item);
item.setOnClickListener(new View.OnClickListener() {
public void onClick(View v) {
Toast.makeText(v.getContext(), "#" + position, Toast.LENGTH_SHORT).show();
}
});
}
At the end we have a result that looks like this:
Two item of list for one row and make text clickable.
Design images for each row and two image for one item.
For each option make only text of both item clickable.
I do not think is is possible to create actual triangle shaped views and add them to list view. And what layout would you use in such a scenario?
One way to do it is using background images to create an illusion. Think of each segment separated by red lines as an item in list view. Therefore, you'll have to create the background images as required for each item in the list view and set them in the correct order.
Update: This is what i mean by consistent slicing of the background image in my comments below.
For those who are now using RecyclerView, an implementation of ItemDecoration can be set on RecyclerView which will:
Offset items : Override getItemOffsets() of decoration. Here one can move items so that they overlap.
Draw shapes : Override onDraw() to draw triangles as background.
I'm trying to create a custom view with the height bigger than the display's (or parent's) height. This view must be scrollable. I use onTouchEvent to measure new finger position, and I actually get what I want, but my view doesn't move. I've searched in the internet for the problem's solution, but didn't find a suitable answer.
This's my View
public class SmartScroll extends View{
public SmartScroll(Context context) {
super(context);
}
int count = 0;
int yStart = 0, yEnd = 0;
#Override
public boolean onTouchEvent(MotionEvent event) {
switch(event.getAction()) {
case MotionEvent.ACTION_DOWN:
yStart = (int) event.getY();
break;
case MotionEvent.ACTION_MOVE:
MeasureSpec.toString(getMeasuredHeight());
MeasureSpec.toString(getMeasuredWidth());
count++;
yEnd = (int) event.getY();
scrollBy(0, (yEnd - yStart));
yStart = yEnd;
break;
case MotionEvent.ACTION_UP:
yStart = 0;
yEnd = 0;
break;
}
return true;
}
and this's how I create it inActivity:
public class MyActivity extends Activity {
#Override
public void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
SmartScroll scrollView = new SmartScroll(getApplicationContext()) {
#Override
protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
super.onMeasure(widthMeasureSpec, MeasureSpec.makeMeasureSpec(1500, MeasureSpec.EXACTLY));
}
};
scrollView.setBackgroundDrawable(getResources().getDrawable(R.drawable.gradient));
setContentView(scrollView);
}
}
Please, help me to make it work
Unfortunately I didn't find the answer on my question. I tried some working examples from the internet which use scrollBy() or scrollTo() and they really work.
So, I found solution that works for me. As I couldn't move the content of my custom view, I decided to move the whole view.
I do this by listening to onTouchEvents, and setting new Y coordinates of my view. After setting new Y I call invalidate().
UPDATE:
I finally found the reason of my initial problem. It was in the way I created my custom ViewGroup. I set height of my ViewGroup bigger than the height of its parent and wanted to scroll its content. But content was the same size of the very ViewGroup, so scrollBy() didn't find the need to scroll content.
What I did was I filled my ViewGroup with children so that the content became bigger than the view, measured my ViewGroup to MatchParent and scrollBy() finally started working.
In my implementation, I have two images, one layed over the other. As, I move a circle object over the top image, I want to make that area inside circle transperent, so that I can see the beneath image. For example I have two images - a car image and its framework image. I overlay car image over framweork image and as i drag a circle over car image, it should show the beneath framework.
I tried to search a lot but not getting any pointers. Somewhere I read that I need to use alpha masking or image masking using porterduff and xfermode. but I did not understand.
Specifically,
How can I make the above image transperent and how can I only make the area inside circle transperent?
Thank You
I've used helpful inputs from the question PorterduffXfermode: Clear a section of a bitmap. In the example below, touch area becomes 'transparent' and it's possible to observe down_image part beneath up_image (both images are just jpg drawables in resources).
Basically there's two possible implementations:
Disabling hardware acceleration of drawing which would make PorterDuff.Mode.CLEAR to make view transparent (refer to here for more details about hardware acceleration effects):
Activity:
public class MyActivity extends Activity {
/** Overlay image */
private DrawingView mDrawingView = null;
#Override
public void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
requestWindowFeature(Window.FEATURE_NO_TITLE);
final RelativeLayout.LayoutParams params = new RelativeLayout.LayoutParams(ViewGroup.LayoutParams.MATCH_PARENT,
ViewGroup.LayoutParams.MATCH_PARENT);
// Create the view for the xfermode test
mDrawingView = new DrawingView(this);
params.addRule(RelativeLayout.CENTER_IN_PARENT);
mDrawingView.setLayoutParams(params);
final RelativeLayout relativeLayout = new RelativeLayout(this);
relativeLayout.setBackgroundResource(R.drawable.down_image);
relativeLayout.addView(mDrawingView);
// Main part of the implementation - make layer drawing software
if (android.os.Build.VERSION.SDK_INT >= 11) {
mDrawingView.setLayerType(View.LAYER_TYPE_SOFTWARE, null);
}
// Show the layout with the test view
setContentView(relativeLayout);
}
}
DrawingView:
/**
* View which makes touch area transparent
*/
public class DrawingView extends View {
/** Paint to clear touch area */
private Paint mClearPaint = null;
/** Main bitmap */
private Bitmap mBitmap = null;
/** X coordinate of touch circle */
private int mXTouch = -1;
/** Y coordinate of touch circle */
private int mYTouch = -1;
/** Radius of touch circle */
private int mRadius = 0;
/**
* Default constructor
*
* #param ct {#link Context}
*/
public DrawingView(final Context ct) {
super(ct);
// Generate bitmap used for background
mBitmap = BitmapFactory.decodeResource(ct.getResources(), R.drawable.up_image);
// Generate array of paints
mClearPaint = new Paint();
mClearPaint.setARGB(255, 255, 255, 0);
mClearPaint.setStrokeWidth(20);
mClearPaint.setStyle(Paint.Style.FILL);
// Set all transfer modes
mClearPaint.setXfermode(new PorterDuffXfermode(PorterDuff.Mode.CLEAR));
mRadius = getResources().getDimensionPixelSize(R.dimen.radius);
}
#Override
public void onDraw(final Canvas canv) {
// Background colour for canvas
canv.drawColor(Color.argb(255, 0, 0, 0));
// Draw the bitmap leaving small outside border to see background
canv.drawBitmap(mBitmap, null, new RectF(0, 0, getMeasuredWidth(), getMeasuredHeight()), null);
// Loop, draws 4 circles per row, in 4 rows on top of bitmap
// Drawn in the order of mClearPaint (alphabetical)
if (mXTouch > 0 && mYTouch > 0) {
canv.drawCircle(mXTouch, mYTouch, mRadius, mClearPaint);
}
}
#Override
public boolean onTouchEvent(final MotionEvent event) {
boolean handled = false;
// get touch event coordinates and make transparent circle from it
switch (event.getActionMasked()) {
case MotionEvent.ACTION_DOWN:
case MotionEvent.ACTION_MOVE:
mXTouch = (int) event.getX();
mYTouch = (int) event.getY();
// TODO: Note, in case of large scene it's better not to use invalidate without rectangle specified
invalidate();
handled = true;
break;
case MotionEvent.ACTION_UP:
case MotionEvent.ACTION_POINTER_UP:
case MotionEvent.ACTION_CANCEL:
mXTouch = -1;
mYTouch = -1;
// TODO: Note, in case of large scene it's better not to use invalidate without rectangle specified
invalidate();
handled = true;
break;
default:
// do nothing
break;
}
return super.onTouchEvent(event) || handled;
}
}
Adopt this solution, but looks like it depends on Android version, because suggested solution hasn't worked on mine 4.2 device at all.
I've been struggling on the last couple of days with this program. The issue that I'm facing is about a drop event of a view which excludes another view. What the program is supposed to do is, on a click of a button, creates as many ImageButtons and they all can be dragged and dropped anywhere on the screen. However, he creates the first view and creates the second, but when I drag the second ImageButton, the first one created is deleted from the screen.
Here is the XML
<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"
android:id="#+id/relative_main"
tools:context=".WhyItActivity" >
<Button
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="add button"
android:onClick="addButton" />
</RelativeLayout>
Below is the java activity responsible
public class WhyItActivity extends Activity {
private RelativeLayout relative;
private LayoutParams params;
#Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_why_it);
params = new LayoutParams(LayoutParams.WRAP_CONTENT, LayoutParams.WRAP_CONTENT);
relative = (RelativeLayout)findViewById(R.id.relative_main);
// all the screen be a dropping area
relative.setOnDragListener(new View.OnDragListener() {
#Override
public boolean onDrag(View view, DragEvent event) {
View dragView = (View) event.getLocalState();
RelativeLayout layout = (RelativeLayout) view;
switch (event.getAction()) {
case DragEvent.ACTION_DRAG_STARTED:
break;
case DragEvent.ACTION_DRAG_ENTERED:
break;
case DragEvent.ACTION_DRAG_EXITED:
break;
case DragEvent.ACTION_DROP:
int right = (int)event.getX();
int top = (int)event.getY();
params.setMargins(right, top, 0, 0);
layout.removeView(dragView);
layout.addView(dragView, params);
// set back the visibility
dragView.setVisibility(View.VISIBLE);
break;
case DragEvent.ACTION_DRAG_ENDED:
break;
default:
break;
}
return true;
}
});
}
/** OnClick */
public void addButton(View v){
// params for centralizing the image button
LayoutParams params = new LayoutParams(LayoutParams.WRAP_CONTENT, LayoutParams.WRAP_CONTENT);
params.addRule(RelativeLayout.CENTER_IN_PARENT);
// new object
ImageButton imgBtn = new ImageButton(this);
imgBtn.setImageDrawable(getResources().getDrawable(R.drawable.ic_launcher));
relative.addView(imgBtn, params);
// letting the view be dragged around the screen
imgBtn.setOnLongClickListener(new View.OnLongClickListener() {
#Override
public boolean onLongClick(View view) {
ClipData data = ClipData.newPlainText("", "");
DragShadowBuilder builder = new View.DragShadowBuilder(view);
view.startDrag(data, builder, view, 0);
// let the view be invisible while dragging the object
view.setVisibility(View.INVISIBLE);
return true;
}
});
}
So, the question is... Where I'm screwing? I create a new object, if it's was a matter of just one view on the screen it would be working just fine.
Thanks for reading so far, and possibly be able to help.
One silly but critical mistake.
Add this line
params = params = new LayoutParams(LayoutParams.WRAP_CONTENT,
LayoutParams.WRAP_CONTENT);
Above
params.setMargins(right, top, 0, 0);
As u can see there is only one instance of LayoutParam you have been assigning to all your views, thus ending up in stack of views.
Please mark as accepted if resolved. Thanks.
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);
}
}