How to position the dropdown on a multi-line AutoCompleteTextView? - android

I am trying to customize the behavior of an AutoCompleteTextView, but I have run into some difficulties. My AutoCompleteTextView subclass is a multi-line edit text, and I want to be able to vertically position the dropdown (really a ListPopupWindow) so as to follow the user's cursor around.
I thought that if I can compute the difference between the top of the anchor view and the current line position, I could set the offset accordingly, but I must be doing something wrong along the way, because the dropdown isn't going quite where I want it to go. Can someone point out to me what I'm doing wrong in this code?
#Override
public void showDropDown() {
// Get the screen position of the cursor and set the vertical offset accordingly.
int[] screenPoint = new int[2];
getLocationOnScreen(screenPoint);
final Rect displayFrame = new Rect();
getWindowVisibleDisplayFrame(displayFrame);
int pos = getSelectionStart();
Layout layout = getLayout();
int line = layout.getLineForOffset(pos);
int lineTop = layout.getLineTop(Math.max(0,line - 1));
int lineBottom = layout.getLineBottom(line);
int availableSpaceAboveField = screenPoint[1] + lineTop - displayFrame.top;
int availableSpaceBelowField = displayFrame.bottom - screenPoint[1] - lineBottom;
int verticalOffset = -lineTop;
setDropDownVerticalOffset(verticalOffset);
super.showDropDown();
}

Related

How to know if edittext is scrolled to bottom?

In Android, how can I know if my edittext field is scrolled to the bottom, i.e. the last line is fully visible?
I tried this, but it seems to be too much code for a simple thing:
EditText et;
int scrollY = et.getScrollY();
int sum = et.getHeight() + scrollY;
Layout layout = et.getLayout();
int lastVisibleLineNumber = layout.getLineForVertical(sum);
int lineBottom = layout.getLineBottom(lastVisibleLineNumber);
int diff = lineBottom + layout.getBottomPadding() + et.getPaddingBottom() + et.getPaddingTop() - sum;
if (diff <= 0) // Scrolled to the bottom
you can use
android:ellipsize="end"
to get bottom of edit text.
You can try
int diff = et.getBottom() - (et.getHeight() + et.getScrollY());

Get the real X/Y position of an onTouch Event

I know there are some similar topics that have been posted, but I couldn’t find a good solution for my problem.
I have a GridView which is filed with a custom ImageAdapter. Everything works fine, but whenever I click on an image contained in the GridView, I would like to move another Imageview at the click's position.
However, the coordinates of the Event, that I take with event.getX() and event.getY(), don’t correspond to the click’s position.
I first thought of a problem of dp/px conversion, and I tried several solutions in this way but none of them worked.
Then I tried to use the getXPrecision(), but I couldn’t make a working solution…
Maybe there is another way?
I would like to make the correct position programmatically, without adding constants int, so my project will work on various phone and tablet, with different dp and resolutions.
EDIT : Here is a screenshot, where i clicked the 3rd cell of the first line, and setted the position of the pencil with getRawX() - getRawY(). As we can see, this is not the correct position, I want the red dot (imageview's center) to be positionned where i clicked.
The code used :
//getting the position of the onTouch event :
GridView centre = (GridView) findViewById(R.id.gridView);
adapter = new ImageAdapter(this, (dim * dim), tailleCell);
centre.setAdapter(adapter);
centre.setOnTouchListener(new View.OnTouchListener() {
public boolean onTouch(View view, MotionEvent event) {
int X = (int)event.getRawX();
int Y = (int)event.getRawY();
animation(X, Y, etat);
return false;
}
});`
//launching the animation (and setting position of the pencil) :
private void animation(int posX, int posY, int etat)
{
final ImageView img;
if(etat == 0)
{
img = (ImageView) findViewById(R.id.imageView);
}
else
{
img = (ImageView) findViewById(R.id.imageView2);
}
img.clearAnimation();
img.setVisibility(View.VISIBLE);
img.setX(posX);
img.setY(posY);
[...]
}
EDIT 2 : ~Solution :
Jesus Molina Rodríguez De Vera's solution wasn't working as expected, but i managed to make a workable solution. I just changed my code in the Event Handler to adjust the image's position :
int[] offset = new int[2];
centre.getLocationOnScreen(offset);
int Xoffset=offset[0];
int Yoffset = offset[1];
int X = (int)event.getRawX();
int Y = (int)event.getRawY();
animation(X-((int)Math.round(Xoffset/1.15)), Y-((int)Math.round(Yoffset/1.5)), etat);
Sorry for my bad English :)
Thanks for your help!
Try using getRawX() and getRawY() instead of getX and getY.
Edit
I think that i have found the problem.
You are obtaining X and Y relative to the GridView top-left corner, not to the absolute screen coordinetes.
What you can do is the following:
int[] offset = new int[2];
center.getLocationOnScreen(offset);
int Xoffset=offset[0];
int Yoffset = offset[1];
private void animation(int posX, int posY, int etat){
//...
img.setX(posX+Xoffset);
img.setY(posY+Yoffset);
[...]
}
This is supposed to set the the top-left corner of the ImageView in the selected point. In order to set the center in that point:
int ivWidth = img.getWidth();
int ivHeight = img.getHeight();
private void animation(int posX, int posY, int etat){
//...
int[] finalPosition=new int[2];
finalPosition[0] = posX+Xoffset-(ivWidth/2);
finalPosition[1] = posY+Yoffset-(ivHeight/2);
img.setX(finalPosition[0]);
img.setY(finalPosition[1]);
[...]
}
I haven't try it but it should work.
Edit 2
Xoffset and Yoffset are only needed if you use getX()/getY() instead of getRawX()/getRawY()

Implementing a multicolumn ListView with independent Row-heights

I would like to create a list of about 200 ImageViews (random heights) with the following layout in a 'collage' fashion:
Normally I would do this in a ListView for the peformance gained by using Adapters but since i want the images to be displayed in columns, and with different height (See picture Example ) depending on the pictures, I cannot use a single listview for this purpose.
I have tried implementing this layout with:
Three ListViews with synchronized scrolling = Slow
Single ListView with each row containing three images = Not allowing different heights
GridView = Not allowing different heights
GridLayout = Difficult to implement different heights programmatically. Because of no adapter, OutOfMemoryErrors are common
FlowLayout = Because of no adapter, OutOfMemoryErrors are common
ScrollView with three Vertical LinearLayouts = Best solution so far, but OutOfMemoryErrors are common
I have ended up using three LinearLayouts in a ScrollView, but this is far from optimal. I would rather use something with an Adapter.
EDIT
I have been looking at the StaggeredGridView, as in a response below, but I find it quite buggy. Are there any implementations of this that are more stable?
I think I have a working solution for you.
The main files mentioned here are also on PasteBin at http://pastebin.com/u/morganbelford
I basically implemented a simplified equivalent of the github project mentioned, https://github.com/maurycyw/StaggeredGridView, using a set of excellent LoopJ SmartImageViews.
My solution is not nearly as generic and flexible as the StaggeredGridView, but seems to work well, and quickly. One big difference functionally is that we layout the images always just left to right, then left to right again. We don't try to put the next image in the shortest column. This makes the bottom of the view a little more uneven, but generates less shifting around during initial load from the web.
There are three main classes, a custom StagScrollView, which contains a custom StagLayout (subclassed FrameLayout), which manages a set of ImageInfo data objects.
Here is our layout, stag_layout.xml (the 1000dp initial height is irrelevant, since it will get recomputed in code based on the image sizes):
// stag_layout.xml
<?xml version="1.0" encoding="utf-8"?>
<com.morganbelford.stackoverflowtest.pinterest.StagScrollView xmlns:a="http://schemas.android.com/apk/res/android"
a:id="#+id/scroller"
a:layout_width="match_parent"
a:layout_height="match_parent" >
<com.morganbelford.stackoverflowtest.pinterest.StagLayout
a:id="#+id/frame"
a:layout_width="match_parent"
a:layout_height="1000dp"
a:background="#drawable/pinterest_bg" >
</com.morganbelford.stackoverflowtest.pinterest.StagLayout>
</com.morganbelford.stackoverflowtest.pinterest.StagScrollView>
Here is our main Activity's onCreate, which uses the layout. The StagActivity just basically tells the StagLayout what urls to use, what the margin should be between each image, and how many columns there are. For more modularity, we could have passed these params to the StagScrollView (which contains the StagLayout, but the the scroll view would have just had to pass them down the layout anyway):
// StagActivity.onCreate
setContentView(R.layout.stag_layout);
StagLayout container = (StagLayout) findViewById(R.id.frame);
DisplayMetrics metrics = new DisplayMetrics();
((WindowManager)getSystemService(Context.WINDOW_SERVICE)).getDefaultDisplay().getMetrics(metrics);
float fScale = metrics.density;
String[] testUrls = new String[] {
"http://www.westlord.com/wp-content/uploads/2010/10/French-Bulldog-Puppy-242x300.jpg",
"http://upload.wikimedia.org/wikipedia/en/b/b0/Cream_french_bulldog.jpg",
"http://bulldogbreeds.com/breeders/pics/french_bulldog_64368.jpg",
"http://www.drsfostersmith.com/images/articles/a-french-bulldog.jpg",
"http://2.bp.blogspot.com/-ui2p5Z_DJIs/Tgdo09JKDbI/AAAAAAAAAQ8/aoTdw2m_bSc/s1600/Lilly+%25281%2529.jpg",
"http://www.dogbreedinfo.com/images14/FrenchBulldog7.jpg",
"http://dogsbreed.net/wp-content/uploads/2011/03/french-bulldog.jpg",
"http://www.theflowerexpert.com/media/images/giftflowers/flowersandoccassions/valentinesdayflowers/sea-of-flowers.jpg.pagespeed.ce.BN9Gn4lM_r.jpg",
"http://img4-2.sunset.timeinc.net/i/2008/12/image-adds-1217/alcatraz-flowers-galliardia-m.jpg?300:300",
"http://images6.fanpop.com/image/photos/32600000/bt-jpgcarnation-jpgFlower-jpgred-rose-flow-flowers-32600653-1536-1020.jpg",
"http://the-bistro.dk/wp-content/uploads/2011/07/Bird-of-Paradise.jpg",
"http://2.bp.blogspot.com/_SG-mtHOcpiQ/TNwNO1DBCcI/AAAAAAAAALw/7Hrg5FogwfU/s1600/birds-of-paradise.jpg",
"http://wac.450f.edgecastcdn.net/80450F/screencrush.com/files/2013/01/get-back-to-portlandia-tout.jpg",
"http://3.bp.blogspot.com/-bVeFyAAgBVQ/T80r3BSAVZI/AAAAAAAABmc/JYy8Hxgl8_Q/s1600/portlandia.jpg",
"http://media.oregonlive.com/ent_impact_tvfilm/photo/portlandia-season2jpg-7d0c21a9cb904f54.jpg",
"https://twimg0-a.akamaihd.net/profile_images/1776615163/PortlandiaTV_04.jpg",
"http://getvideoartwork.com/gallery/main.php?g2_view=core.DownloadItem&g2_itemId=85796&g2_serialNumber=1",
"http://static.tvtome.com/images/genie_images/story/2011_usa/p/portlandia_foodcarts.jpg",
"http://imgc.classistatic.com/cps/poc/130104/376r1/8728dl1_27.jpeg",
};
container.setUrls(testUrls, fScale * 10, 3); // pass in pixels for margin, rather than dips
Before we get to the meat of the solution, here is our simple StagScrollView subclass. His only special behavior is to tell his main child (our StagLayout) which the currently visible area is, so that he can efficiently use the smallest possible number of realized subviews.
// StagScrollView
StagLayout _frame;
#Override
protected void onFinishInflate() {
super.onFinishInflate();
_frame = (StagLayout) findViewById(R.id.frame);
}
#Override
protected void onSizeChanged(int w, int h, int oldw, int oldh) {
super.onSizeChanged(w, h, oldw, oldh);
if (oldh == 0)
_frame.setVisibleArea(0, h);
}
#Override
protected void onScrollChanged(int l, int t, int oldl, int oldt) {
super.onScrollChanged(l, t, oldl, oldt);
_frame.setVisibleArea(t, t + getHeight());
}
Here then is the most important class StagLayout.
First, setUrls sets up our data structures.
public void setUrls(String[] urls, float pxMargin, int cCols)
{
_pxMargin = pxMargin;
_cCols = cCols;
_cMaxCachedViews = 2 * cCols;
_infos = new ArrayList<ImageInfo>(urls.length); // should be urls.length
for (int i = 0; i < 200; i++) // should be urls.length IRL, but this is a quick way to get more images, by using repeats
{
final String sUrl = urls[i % urls.length]; // could just be urls[i] IRL
_infos.add(new ImageInfo(sUrl, new OnClickListener() {
#Override
public void onClick(View v) {
Log.d("StagLayout", String.format("Image clicked: url == %s", sUrl));
}
}));
}
_activeInfos = new HashSet<ImageInfo>(_infos.size());
_cachedViews = new ArrayList<SmartImageView>(_cMaxCachedViews);
requestLayout(); // perform initial layout
}
Our main data structure is ImageInfo. It is a kind of lightweight placeholder that allows us to keep track of where each image is going to be displayed, when it needs to be. When we layout our child views, we will use the information in the ImageInfo to figure out where to put the actual view. A good way to think about ImageInfo is as a "virtual image view".
See comments inline for details.
public class ImageInfo {
private String _sUrl;
// these rects are in float dips
private RectF _rLoaded; // real size of the corresponding loaded SmartImageView
private RectF _rDefault; // lame default rect in case we don't have anything better to go on
private RectF _rLayout; // rect that our parent tells us to use -- this corresponds to a real View's layout rect as specified when parent ViewGroup calls child.layout(l,t,r,b)
private SmartImageView _vw;
private View.OnClickListener _clickListener;
public ImageInfo(String sUrl, View.OnClickListener clickListener) {
_rDefault = new RectF(0, 0, 100, 100);
_sUrl = sUrl;
_rLayout = new RectF();
_clickListener = clickListener;
}
// Bounds will be called by the StagLayout when it is laying out views.
// We want to return the most accurate bounds we can.
public RectF bounds() {
// if there is not yet a 'real' bounds (from a loaded SmartImageView), try to get one
if (_rLoaded == null && _vw != null) {
int h = _vw.getMeasuredHeight();
int w = _vw.getMeasuredWidth();
// if the SmartImageView thinks it knows how big it wants to be, then ok
if (h > 0 && w > 0) {
_rLoaded = new RectF(0, 0, w, h);
}
}
if (_rLoaded != null)
return _rLoaded;
// if we have not yet gotten a real bounds from the SmartImageView, just use this lame rect
return _rDefault;
}
// Reuse our layout rect -- this gets called a lot
public void setLayoutBounds(float left, float top, float right, float bottom) {
_rLayout.top = top;
_rLayout.left = left;
_rLayout.right = right;
_rLayout.bottom = bottom;
}
public RectF layoutBounds() {
return _rLayout;
}
public SmartImageView view() {
return _vw;
}
// This is called during layout to attach or detach a real view
public void setView(SmartImageView vw)
{
if (vw == null && _vw != null)
{
// if detaching, tell view it has no url, or handlers -- this prepares it for reuse or disposal
_vw.setImage(null, (SmartImageTask.OnCompleteListener)null);
_vw.setOnClickListener(null);
}
_vw = vw;
if (_vw != null)
{
// We are attaching a view (new or re-used), so tell it its url and attach handlers.
// We need to set this OnCompleteListener so we know when to ask the SmartImageView how big it really is
_vw.setImageUrl(_sUrl, R.drawable.default_image, new SmartImageTask.OnCompleteListener() {
final private View vw = _vw;
#Override
public void onComplete() {
vw.measure(MeasureSpec.makeMeasureSpec(LayoutParams.WRAP_CONTENT, MeasureSpec.UNSPECIFIED), MeasureSpec.makeMeasureSpec(LayoutParams.WRAP_CONTENT, MeasureSpec.UNSPECIFIED));
int h = vw.getMeasuredHeight();
int w = vw.getMeasuredWidth();
_rLoaded = new RectF(0, 0, w, h);
Log.d("ImageInfo", String.format("Settings loaded size onComplete %d x %d for %s", w, h, _sUrl));
}
});
_vw.setOnClickListener(_clickListener);
}
}
// Simple way to answer the question, "based on where I have laid you out, are you visible"
public boolean overlaps(float top, float bottom) {
if (_rLayout.bottom < top)
return false;
if (_rLayout.top > bottom)
return false;
return true;
}
}
The rest of the magic happens in StagLayout's onMeasure and onLayout.
#Override
protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
int width = MeasureSpec.getSize(widthMeasureSpec);
// Measure each real view that is currently realized. Initially there are none of these
for (ImageInfo info : _activeInfos)
{
View v = info.view();
v.measure(MeasureSpec.makeMeasureSpec(LayoutParams.WRAP_CONTENT, MeasureSpec.UNSPECIFIED), MeasureSpec.makeMeasureSpec(LayoutParams.WRAP_CONTENT, MeasureSpec.UNSPECIFIED));
}
// This arranges all of the imageinfos every time, and sets _maxBottom
//
computeImageInfo(width);
setMeasuredDimension(width, (int)_maxBottom);
}
#Override
protected void onLayout(boolean changed, int l, int t, int r, int b) {
// This figures out what real SmartImageViews we need, creates new ones, re-uses old ones, etc.
// After this call _activeInfos is correct -- the list of ImageInfos that are currently attached to real SmartImageViews
setupSubviews();
for (ImageInfo info : _activeInfos)
{
// Note: The layoutBounds of each info is actually computed in onMeasure
RectF rBounds = info.layoutBounds();
// Tell the real view where it should be
info.view().layout((int)rBounds.left, (int)rBounds.top, (int)rBounds.right, (int)rBounds.bottom);
}
}
Ok, now let's see how we actually arrange all the ImageInfos.
private void computeImageInfo(float width)
{
float dxMargin = _pxMargin;
float dyMargin = _pxMargin;
float left = 0;
float tops[] = new float[_cCols]; // start at 0
float widthCol = (int)((width - (_cCols + 1) * dxMargin) / _cCols);
_maxBottom = 0;
// layout the images -- set their layoutrect based on our current location and their bounds
for (int i = 0; i < _infos.size(); i++)
{
int iCol = i % _cCols;
// new row
if (iCol == 0)
{
left = dxMargin;
for (int j = 0; j < _cCols; j++)
tops[j] += dyMargin;
}
ImageInfo info = _infos.get(i);
RectF bounds = info.bounds();
float scale = widthCol / bounds.width(); // up or down, for now, it does not matter
float layoutHeight = bounds.height() * scale;
float top = tops[iCol];
float bottom = top + layoutHeight;
info.setLayoutBounds(left, top, left + widthCol, bottom);
if (bottom > _maxBottom)
_maxBottom = bottom;
left += widthCol + dxMargin;
tops[iCol] += layoutHeight;
}
// TODO Optimization: build indexes of tops and bottoms
// Exercise for reader
_maxBottom += dyMargin;
}
And, now let's see how we create, resuse and dispose of real SmartImageViews during onLayout.
private void setupSubviews()
{
// We need to compute new set of active views
// TODO Optimize enumeration using indexes of tops and bottoms
// NeededInfos will be set of currently visible ImageInfos
HashSet<ImageInfo> neededInfos = new HashSet<ImageInfo>(_infos.size());
// NewInfos will be subset that are not currently assigned real views
HashSet<ImageInfo> newInfos = new HashSet<ImageInfo>(_infos.size());
for (ImageInfo info : _infos)
{
if (info.overlaps(_viewportTop, _viewportBottom))
{
neededInfos.add(info);
if (info.view() == null)
newInfos.add(info);
}
}
// So now we have the active ones. Lets get any we need to deactivate.
// Start with a copy of the _activeInfos from last time
HashSet<ImageInfo> unneededInfos = new HashSet<ImageInfo>(_activeInfos);
// And remove all the ones we need now, leaving ones we don't need any more
unneededInfos.removeAll(neededInfos);
// Detach all the views from these guys, and possibly reuse them
ArrayList<SmartImageView> unneededViews = new ArrayList<SmartImageView>(unneededInfos.size());
for (ImageInfo info : unneededInfos)
{
SmartImageView vw = info.view();
unneededViews.add(vw);
info.setView(null); // at this point view is still a child of parent
}
// So now we try to reuse the views, and create new ones if needed
for (ImageInfo info : newInfos)
{
SmartImageView vw = null;
if (unneededViews.size() > 0)
{
vw = unneededViews.remove(0); // grab one of these -- these are still children and so dont need to be added to parent
}
else if (_cachedViews.size() > 0)
{
vw = _cachedViews.remove(0); // else grab a cached one and re-add to parent
addViewInLayout(vw, -1, new FrameLayout.LayoutParams(LayoutParams.WRAP_CONTENT, LayoutParams.WRAP_CONTENT));
}
else
{
vw = new SmartImageView(getContext()); // create a whole new one
FrameLayout.LayoutParams lp = new FrameLayout.LayoutParams(LayoutParams.WRAP_CONTENT, LayoutParams.WRAP_CONTENT);
addViewInLayout(vw, -1, lp); // and add to parent
}
info.setView(vw); // info should also set its data
}
// At this point, detach any unneeded views and add to our cache, up to limit
for (SmartImageView vw : unneededViews)
{
// tell view to cancel
removeViewInLayout(vw); // always remove from parent
if (_cachedViews.size() < _cMaxCachedViews)
_cachedViews.add(vw);
}
// Record the active ones for next time around
_activeInfos = neededInfos;
}
Remember that _viewportTop and _viewportBottom are set every time the user scrolls.
// called on every scroll by parent StagScrollView
public void setVisibleArea(int top, int bottom) {
_viewportTop = top;
_viewportBottom = bottom;
//fixup views
if (getWidth() == 0) // if we have never been measured, dont do this - it will happen in first layout shortly
return;
requestLayout();
}
You can have a look at https://github.com/maurycyw/StaggeredGridView
I have not worked with it personally, but you could atleast steal some concepts.
Create a list view in a layout.
Create another layout with same background as that of list view background layout with three Image Views (next to each other ie to the right of each other) with their properties set to Wrap_Content horizontally and the whole Views properties in which image views are put to Wrap_Content.
Inflate the layout in the getview() method of listview adapter. In this you need to set 3 set of images in Image Views of the inflated Layout.
Hope this helps!
I guess it can be implemented with three independent list view, only thing which you have to do it to inflate layout for imageview and add it to listview.
use following as layout parameters during inflation.
Layout Width : match_parent
layout Height: wrap_content
you can assign layout weight as .3 for all the three list view with layout_width as 0dp and height as fill_parent.
hope this helps.
Can't you use your current solution wrapped in a custom list ?
in getView method for each row inflate your existing solution (checking converview ofcourse)
i.e. ScrollView with three Vertical LinearLayouts.
Do you know why the 3 List View solution was slow?
How many different sizes are in each column? I think that for the recycling of views to be efficient, you would want to create a view type for each size of image, and then make sure that you use getItemViewType, to be sure that you're recycling the correct type of view. Otherwise, you will not get much benefit from the recycling. You would want to be able to just reset the source for the image view.

Get absolute position for a given offset on TextView (Android)

I have a TextView in which I want to place a solid color block over given words of the TextView, for example:
"This is a text string, I want to put a rectangle over this WORD" - so, "WORD" would have a rectangle with a solid color over it.
To do this, I am thinking about overriding the onDraw(Canvas canvas) method, in order to draw a block over the text. My only problem is to find an efficient way to get the absolute position of a given word or character.
Basically, I am looking for something that does the exact opposite of the getOffsetForPosition(float x, float y) method
Based on this post: How get coordinate of a ClickableSpan inside a TextView?, I managed to use this code in order to put a rectangle on top of the text:
protected void onDraw(Canvas canvas) {
super.onDraw(canvas);
Paint paint = new Paint();
paint.setStyle(Paint.Style.FILL);
paint.setColor(Color.WHITE);
// Initialize global value
TextView parentTextView = this;
Rect parentTextViewRect = new Rect();
// Find where the WORD is
String targetWord = "WORD";
int startOffsetOfClickedText = this.getText().toString().indexOf(targetWord);
int endOffsetOfClickedText = startOffsetOfClickedText + targetWord.length();
// Initialize values for the computing of clickedText position
Layout textViewLayout = parentTextView.getLayout();
double startXCoordinatesOfClickedText = textViewLayout.getPrimaryHorizontal((int)startOffsetOfClickedText);
double endXCoordinatesOfClickedText = textViewLayout.getPrimaryHorizontal((int)endOffsetOfClickedText);
// Get the rectangle of the clicked text
int currentLineStartOffset = textViewLayout.getLineForOffset((int)startOffsetOfClickedText);
int currentLineEndOffset = textViewLayout.getLineForOffset((int)endOffsetOfClickedText);
boolean keywordIsInMultiLine = currentLineStartOffset != currentLineEndOffset;
textViewLayout.getLineBounds(currentLineStartOffset, parentTextViewRect);
// Update the rectangle position to his real position on screen
int[] parentTextViewLocation = {0,0};
parentTextView.getLocationOnScreen(parentTextViewLocation);
double parentTextViewTopAndBottomOffset = (
//parentTextViewLocation[1] -
parentTextView.getScrollY() +
parentTextView.getCompoundPaddingTop()
);
parentTextViewRect.top += parentTextViewTopAndBottomOffset;
parentTextViewRect.bottom += parentTextViewTopAndBottomOffset;
// In the case of multi line text, we have to choose what rectangle take
if (keywordIsInMultiLine){
WindowManager wm = (WindowManager) getContext().getSystemService(Context.WINDOW_SERVICE);
Display display = wm.getDefaultDisplay();
int screenHeight = display.getHeight();
int dyTop = parentTextViewRect.top;
int dyBottom = screenHeight - parentTextViewRect.bottom;
boolean onTop = dyTop > dyBottom;
if (onTop){
endXCoordinatesOfClickedText = textViewLayout.getLineRight(currentLineStartOffset);
}
else{
parentTextViewRect = new Rect();
textViewLayout.getLineBounds(currentLineEndOffset, parentTextViewRect);
parentTextViewRect.top += parentTextViewTopAndBottomOffset;
parentTextViewRect.bottom += parentTextViewTopAndBottomOffset;
startXCoordinatesOfClickedText = textViewLayout.getLineLeft(currentLineEndOffset);
}
}
parentTextViewRect.left += (
parentTextViewLocation[0] +
startXCoordinatesOfClickedText +
parentTextView.getCompoundPaddingLeft() -
parentTextView.getScrollX()
);
parentTextViewRect.right = (int) (
parentTextViewRect.left +
endXCoordinatesOfClickedText -
startXCoordinatesOfClickedText
);
canvas.drawRect(parentTextViewRect, paint);
}
You can use spans for that.
First you create a spannable for your text, like this:
Spannable span = new SpannableString(text);
Then you put a span around the word that you want to highlight, somewhat like this:
span.setSpan(new UnderlineSpan(), start, end,
Spanned.SPAN_EXCLUSIVE_EXCLUSIVE);
Unfortunately I don't know of an existing span that puts a border around a word. I found UnderlineSpan, and also BackgroundColorSpan, perhaps these are also useful for you, or you can have a look at the code and see if you can create a BorderSpan based on one of those.
Instead of drawing a rectangle over the WORD, you could simply replace its characters with an appropriate unicode symbol like U+25AE (▮ Black vertical rectangle).
So you'd get
"This is a text string, I want to put a rectangle over this ▮▮▮▮"
If that is sufficient. See for example Wikipedia for a wast list of unicode symbols.
If you actually need to paint that black box you can do the following as long as your text is in a single line:
Calculate the width of the text part before 'WORD' as explained here to find the left edge of the box and calcuate the width of 'WORD' using the same method to find the width of the box.
For a multiline text the explained method might also work but I think you'll have to do quite a lot of work here.
use getLayout().getLineBottom and textpaint.measureText to manually do the reverse calculation of getOffsetForPosition.
below is an example of using the calculated x,y for some textOffset to position the handle drawable when the textview gets clicked.
class TextViewCustom extends TextView{
float lastX,lastY;
#Override
public boolean onTouchEvent(MotionEvent event) {
boolean ret = super.onTouchEvent(event);
lastX=event.getX();
lastY=event.getY();
return ret;
}
BreakIterator boundary;
Drawable handleLeft;
private void init() {// call it in constructors
boundary = BreakIterator.getWordInstance();
handleLeft=getResources().getDrawable(R.drawable.abc_text_select_handle_left_mtrl_dark);
setOnClickListener(new OnClickListener() {
#Override
public void onClick(View v) {
int line = getLayout().getLineForVertical((int) lastY);
int offset = getLayout().getOffsetForHorizontal(line, lastX);
int wordEnd = boundary.following(offset);
int wordStart = boundary.previous();
CMN.Log(getText().subSequence(wordStart, wordEnd));
int y = getLayout().getLineBottom(line);
int trimA = getLayout().getLineStart(line);
float x = getPaddingLeft()+getPaint().measureText(getText(), trimA, wordStart);
x-=handleLeft.getIntrinsicWidth()*1.f*9/12;
handleLeft.setBounds((int)x,y,(int)(x+handleLeft.getIntrinsicWidth()),y+handleLeft.getIntrinsicHeight());
invalidate();
}
});
}
#Override
public void setText(CharSequence text, BufferType type) {
super.setText(text, type);
if(boundary!=null)
boundary.setText(text.toString());
}
}

Incorrect Coordinates From getLocationOnScreen/getLocationInWindow

A call to getLocationOnScreen() or getLocationInWindow() both give me a top/Y coordinate that is about ~30px (status/notifications bar's height) too far down. The left/X coordinate is dead on.
As I hinted above, I believe the difference is because of the status/notification bar... I could be wrong. I think I can solve this if I can determine the size of the notification bar but, I'm having trouble doing just that.
Any help would be greatly appreciated.
I ended up solving this issue by determining the height of the status/notification bar like so:
View globalView = ...; // the main view of my activity/application
DisplayMetrics dm = new DisplayMetrics();
this.getWindowManager().getDefaultDisplay().getMetrics(dm);
int topOffset = dm.heightPixels - globalView.getMeasuredHeight();
View tempView = ...; // the view you'd like to locate
int[] loc = new int[2];
tempView.getLocationOnScreen(loc);
final int y = loc[1] - topOffset;
Here is how I like to get the status bar height, and adjust the offset:
final int[] location = new int[2];
anchor.getLocationInWindow(location); // Includes offset from status bar, *dumb*
Rect anchorRect = new Rect(location[0], location[1],
location[0] + anchor.getWidth(), location[1] + anchor.getHeight());
anchor.getRootView().findViewById(android.R.id.content).getLocationInWindow(location);
int windowTopOffset = location[1];
anchorRect.offset(0, -windowTopOffset);
I am having the same problem, try using
offset = myView.GetOffsetY();
and adjust your Y coord by that value, e.g.
coordY -= offset;
The class which offers the ``-method:
class MyView extends View {
public int GetOffsetY() {
int mOffset[] = new int[2];
getLocationOnScreen( mOffset );
return mOffset[1];
}
}
This answer does not include how to get the status bar height, but it does explain the behavior of getLocationOnScreen() and getLocationInWindow() returning the same value.
In the case of a normal activity (not a dialog) you should expect these two methods to return the same value. A Window lays out underneath (as in z-order not y coordinates) the status bar, so these methods cannot be used to determine the height of the status bar.
https://stackoverflow.com/a/20154562/777165
As #ThammeGowda said that the correct answer will give incorrect position when the keyboard is shown but you still need to calculate the top offset of the action bar, so just get the action bar size directly
fun View.getPosition(): Point {
val tv = TypedValue()
if (context.theme.resolveAttribute(android.R.attr.actionBarSize, tv, true)) {
val actionBarHeight =
TypedValue.complexToDimensionPixelSize(tv.data, resources.displayMetrics)
val loc = IntArray(2).apply { getLocationInWindow(this) }
val newY = loc[1] - actionBarHeight - (measuredHeight / 2) // to get the position in the middle of the view
return Point(loc[0], newY)
}
return Point(0,0)
}
void setOffset(){
int[] point1=new int[2];
view1.getLocationInWindow(point1);
int[] point2=new int[2];
view2.getLocationInWindow(point2);
int leftOffset=point2[0]-point1[0];
int topOffset=point2[1]-point1[1];
// no need to call this function more than once
// set the offset values in global variables
// remenber do't call it in OnCreate method
// this helps to mesure all type of screen size
}
void setLocationOfView(){
// now do your animation or any view to move
int[] point =new int[2];
sourceView.getLocationInWindow(point);
view.setX(point[0]-leftOffset);
view.setY(point[1]-topOffset);
//wow you did it i am glad to help you and sorry for being late
}
Solution:
Put an invisible png ImageView at the top with
android:layout_marginTop="0dp"
Get location on screen of the invisible png. This will be your offset for the y-coordinate bug.
int[] location = new int[2];
invisiblepng.getLocationOnScreen(location);
int sourceY = location[1];
SharedPreferences.Editor editor = sp.edit();
editor.putString("compensateybug", Integer.toString(sourceY));
editor.apply();
and
public static Point getLocationOnScreen(View view) {
int[] location = new int[2];
view.getLocationOnScreen(location);
return new Point(location[0], location[1]);
}
Retrieve Y coordinate value of the "status/notification bar"
String compensateybug = sp.getString("compensateybug", "0");
int yourycoordinate = parseInt(compensateybug);
I have attached an invisible png image file for download at:
https://drive.google.com/file/d/1zxhP1r6ISvKG8Ig8iItDY2x6k5RVo4Nu/view?usp=sharing

Categories

Resources