Prevent Android's TextView from breaking links - android

This question is probably the same as this one, but since none of its answers really solve the problem, I'll ask again.
My app has a TextView that occasionally will display very long URLs. For aesthetic reasons (and since the URLs contain no spaces), the desirable behaviour would be to fill each line completely before jumping to the next one, something like this:
|http://www.domain.com/som|
|ething/otherthing/foobar/|
|helloworld |
What happens instead, is the URL being broke near the bars, as if they were spaces.
|http://www.domain.com/ |
|something/otherthing/ |
|foobar/helloworld |
I tried extending the TextView class and adding a modified version of the breakManually method (found here) to trick the TextView and do what I need, calling it on onSizeChanged (overridden). It works fine, except for the fact that the TextView is inside a ListView. When this custom TextView is hidden by the scrolling and brought back, its content goes back to the original breaking behaviour, due to the view being re-drawn without onSizeChanged being called.
I was able to work-around this problem by calling breakManually inside onDraw. This presents the expected behaviour at all times, but at a high performance cost: since onDraw is called whenever the ListView is scrolled and the breakManually method isn't exactly "lightweight", the scrolling gets unacceptably laggy, even on a high-end quad-core device.
Next step was to crawl through the TextView source code, trying to figure where and how it splits the text, and hopefully override it. This was a complete failure. I (a newbie) spent the whole day fruitlessly looking at code I mostly couldn't understand.
And that brings me here. Can someone please point me the right direction about what I should override (assuming that it's possible)? Or maybe there is a simpler way of achieving what I want?
Here's the breakManually method I mentioned. Due to the use of getWidth(), it only works if called after the view is measured.
private CharSequence breakManually (CharSequence text) {
int width = getWidth() - getPaddingLeft() - getPaddingRight();
// Can't break with a width of 0.
if (width == 0) return text;
Editable editable = new SpannableStringBuilder(text);
//creates an array with the width of each character
float[] widths = new float[editable.length()];
Paint p = getPaint();
p.getTextWidths(editable.toString(), widths);
float currentWidth = 0.0f;
int position = 0;
int insertCount = 0;
int initialLength = editable.length();
while (position < initialLength) {
currentWidth += widths[position];
char curChar = editable.charAt(position + insertCount);
if (curChar == '\n') {
currentWidth = 0.0f;
} else if (currentWidth > width) {
editable.insert(position + insertCount , "\n");
insertCount++;
currentWidth = widths[position];
}
position++;
}
return editable.toString();
}
To everyone who bothered reading this, thanks for your time.

If you're not using a monospace font, then in some cases it's not even possible to align it very well. Since you have no whitespaces in a URL, it's not likely that a Justify-like alignment will solve the problem. I suggest that you use a monospace font for that particular TextView. Then, decide on a fixed number of characters per line and break the string to display with "\n" after those many characters.
This isn't answering your question but it's the smoothest way possible, I guess.

Related

How to make Xamarin.Forms change size of my custom control on Android

My custom control can sometimes change its size. In this case I call RequestLayout and Invalidate. Then in OnMeasure I call SetMeasuredDimension with new height. But here I face two issues:
OnMeasure is called with MeasureSpecMode.Exact.
Even if I ignore MeasureSpecMode and call SetMeasuredDimension with new values control size stays the same.
So my question is if there is a way to tell Forms layout engine to update my controls size?
EDIT:
It is possible to force Forms to update layout by hiding and then showing the control but I'm looking for a less hacky way to fix this.
If we are really talking about Forms and not xamarin.android you can check about how it works here: https://learn.microsoft.com/en-us/xamarin/xamarin-forms/user-interface/layouts/custom
So basically inside your Forms control after you have calculated the size you desire call InvalidateMeasure();
Override OnMeasure and pass the desired size, Forms will do the rest:
//fields used to store the desired size
private double _widthRequest = -1;
private double _heightRequest = -1;
protected override SizeRequest OnMeasure(double widthConstraint, double heightConstraint)
{
if (_widthRequest > 0 && _heightRequest > 0)
{
var wish = new Size(_widthRequest, _heightRequest);
_widthRequest = -1;
_heightRequest = -1;
return new SizeRequest(wish);
}
return base.OnMeasure(widthConstraint, heightConstraint);
}

AS3 AIR: Smooth scrolling for mobile

So, I'm building fairly simple application for portrait mode only, and it will have vertical scrolling for content.
Content is basicly a MovieClip containing image as background and other minor elements (couple small MovieClips as buttons etc).
The problem is that I don't seem to find a smooth way to scroll my content. I would love it to be smooth and accurate, and I've tried numerous different ways for it.
For testing purposes the background I'm using (inside my content MovieClip) is 533x1250 jpg from library.
This is how I add my content mc and backround into it:
scaleXFactor = stage.stageWidth / 640; //my stage is 640x960
...
addChild(content); //which is new MovieClip()
var bg:bg1 = new bg1();
bg.x = p1bg.y = 0;
bg.width = 640 * scaleXFactor;
bg.scaleY = bg.scaleX;
content.width = bg.width;
content.height = bg.height;
content.addChild(bg);
For scrolling I assumed I'd be safe by just simply using MOUSE_DOWN & MOUSE_UP event listeners and basic ENTER_FRAME function like this:
mouseLastPos = stage.mouseY;
if(MouseUpVar == 1){
mousePosDiff = (mouseOriPosY-mouseLastPos);
}
content.y -= mousePosDiff*1.2;
mouseOriPosY = stage.mouseY;
if(MouseUpVar == 0){ //Smoothening which is allowed by MOUSE_UP
if(mousePosDiff > 0){
mousePosDiff -= 0.001;
}else if(mousePosDiff < 0){
mousePosDiff += 0.001;
}
}
if(content.y < (content.height - stage.stageHeight)*-1){
content.y = (content.height - stage.stageHeight)*-1;
}else if(content.y > 0){
content.y = 0;
}
In debug player it's as smooth as you can ever imagine, but in real device (Samsung Note4) it's very laggy and clumsy.
I've also tried freshplanet's script, which is not as laggy, but still a little clumsy, and messes up my scaleY for some reason.
And IScrollerComponent seems to have some issues on AIR, and I didn't get any visual elements on my screen with it.
Rendering settings or cacheAsBitmap didn't seem to have any effect on this.
How should this be done right? Is it normal that this 'small' image (533x1250) causes bad lag?
I really need to use AS3 and AIR for this, so I'm hoping to find a good and "simple" solution (yeah, that doesn't happen too often).
Thanks,
kaarto
Try using cacheAsBitmap=true on your content and scrollRect instead of moving your content.

Bitmap collision detection

I'm trying to make my app detect when two bitmaps intersect each other. This is the code I tried for collision:
//width and height of the two bitmaps
int width1 = bplay1.getWidth();
int height1 = bplay1.getHeight();
int width2 = bbrick1.getWidth();
int height2 = bbrick1.getHeight();
if (height1 < height2)
if ( width1 < width2)
{
currentscore.setText("collision detected");
}
However, the app crashes on start when I use getHeight() and getWidth(). Am I doing something wrong with it?
If it crashes, what output does it generate in the crash (in logcat)?
Assuming it crashes with null pointer exception (which to me seems most likely based on the very terse information you provided), you should make sure your bplay1 and/or bbrick1 objects are not null like so: bplay1 = new MyClass();
Summary: Whenever you post a S.O. question ask yourself this: If I found this post, what information would I be likely to need to solve it? In this specific case there is actually very little to go on.

refreshing of canvas is happening only after touch

I am using ViewPagerparallax class,(for parallax view ) and I am setting the background using pager.setbackgroundAsset(....);
everything works fine....but I have a situation,what i want to achieve is that whenever the fragments go in a pause state and whenever it is resumed,the background should change and hence i am changing the background in the onResume method but seems like change is only visible whenever i try to swipe between fragments ...
this is my code
public void onResume()
{
Activity_Fragments ab = (Activity_Fragments) this.getActivity();
ViewPagerParallax pager=ab.hi();
int arr[]={R.raw.a1,R.raw.a2,R.raw.a3,R.raw.a4,R.raw.a5,R.raw.a6,R.raw.a7,R.raw.a8,
R.raw.a9,R.raw.a10,R.raw.a11,R.raw.a12,R.raw.a13,R.raw.a14,R.raw.a15,R.raw.a16,
R.raw.a17,R.raw.a18,R.raw.a19,R.raw.a20,R.raw.a21,R.raw.a22,R.raw.a23,
R.raw.a24,R.raw.a25,R.raw.a26,R.raw.a27,R.raw.a28,R.raw.a29};
int x=(int)(Math.random() * ((28 ) + 1));
pager.setBackgroundAsset(arr[x]);
A few notes.
First of, I am assuming you are using this: https://github.com/MatthieuLJ/ViewPagerParallax/blob/master/src/com/matthieu/ViewPagerParallax.java
When looking at the source it appears to be that you are doing things correctly.
I few things I can see that could be happening
The max number of pages set is 0, which is the default
The width or height is 0
The background hasn't changed from last time
The application has run out of memory (They say they don't want to use more then 1/5th of the memory
An exception was thrown while creating the bitmap of the image
A few things you could try
For point number 1, if you haven't already, call the set_max_pages with a number you see fit.
Point 2, Is the ViewPager in the activity with width and height set?
Point 3, confirm that you generator is working. (Already done, logic works perfect)
int arr[]={1,2,3,4,5,6,7,8,9,10,11,12,13,14,15,16,17,
18,19,20,21,22,23,24,25,26,27,28,29};
for(int i = 0; i < 100; i++){
Log.d("TEST", String.valueOf(arr[(int)(Math.random() * ((28 ) + 1))]));
}
This returns the desired output, no problem there.
Point 4 & 5, Check logcat, it could say some errors, the tag should say ViewPagerParallax
I would say point 1 could be it, it will draw the background if it hasn't already once you set it.
I'm not really sure how setBackgroundAsset works, but try a call to invalidate(); at the end of onResume(). That will insure that your onDraw() method will be called ASAP and the new background will be redrawn.

Android skips onDraw() when I run my animation in reverse

I have an implementation of the Sliding Fragments DevByte. In addition to sliding the fragment into view, I want to draw a shadow over the content that it's occluding. I have modified the FractionalLinearLayout from the video to measure itself at twice the screen width and layout its children in the right half. During the animation loop, I draw a black rectangle of increasing alpha in the left half and set my X-coordinate to a negative value to bring the right half into view.
My problem is that this works fine when I'm sliding the content into view, but fails when I'm sliding the content back out of view. On the way in, I get exactly the behaviour I want: a translation and a darkening shadow. On the way out, I get only the translation, and the shadow remains just its first frame color all the way through the animation.
What I can see in my logging is that on the way in, the setPercentOnScreen() and onDraw() methods are called alternatingly, just as expected. On the way out however, I get one call to setPercentageOnScreen(), followed by one call to onDraw(), followed by only calls to setPercentOnScreen(). Android is optimizing away the drawing, but I can't figure out why.
updated: What's interesting is that I only see this behaviour on an emulator running Android 4.4. Emulators running 4.0.3 and 4.3 run the animation as intended in both directions. An old Nexus 7 exhibits the problem, a different emulator 4.4 does not. It appears to be consistent on a device, but vary between devices.
updated again: I've extracted a sample project and put it on GitHub: barend/android-slidingfragment. The readme file on GitHub contains test results on a dozen devices. For emulators, the problem correlates to the "Enable Host GPU" feature, but only on Jelly Bean and KitKat; not on ICS.
updated yet again: further testing shows that the problem occurs on physical devices running Jelly Bean and higher, as well as on ARM emulators with "Use Host GPU" enabled running Jelly Bean or higher. It does not occur on x86 emulators and on ARM emulators without "Use Host GPU", regardless of Android version. The exact table of my tests can be found in the github project linked above.
// Imports left out
public class HorizontalSlidingLayout extends FrameLayout {
/**
* The fraction by which the content has slid into view. Legal range: from 0.0 (all content
* off-screen) to 1.0 (all content visible).
*/
private float percentOnScreen;
private int screenWidth, screenHeight, shift;
private Paint shadowPaint;
// Constructors left out, all three call super, then init().
private void init() {
if (isInEditMode()) {
// Ensure content is visible in edit mode.
percentOnScreen = 1.0f;
} else {
setWillNotDraw(false);
percentOnScreen = 0.0f;
shadowPaint = new Paint();
shadowPaint.setAlpha(0x00);
shadowPaint.setColor(0x000000);
shadowPaint.setStyle(Paint.Style.FILL);
}
}
/** Reports our own size as (2w, h) and measures all children at (w, h). */
#Override
protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
screenWidth = MeasureSpec.getSize(widthMeasureSpec);
screenHeight = MeasureSpec.getSize(heightMeasureSpec);
setMeasuredDimension(2 * screenWidth, screenHeight);
for (int i = 0, max = getChildCount(); i < max; i++) {
View child = getChildAt(i);
child.measure(widthMeasureSpec, heightMeasureSpec);
}
}
/** Lays out the children in the right half of the view. */
#Override
protected void onLayout(boolean changed, int left, int top, int right, int bottom) {
for (int i = 0, max = getChildCount(); i < max; i++) {
View child = getChildAt(i);
child.layout(screenWidth, top, right, bottom);
}
}
/**
* Draws a translucent shadow in the left half of the view, darkening by
* {#code percentOnScreen}, then lets superclass draw children in the right half.
*/
#Override
protected void onDraw(Canvas canvas) {
// Maintain 30% translucency
if (percentOnScreen < 0.7f) {
shadowPaint.setAlpha((int) (percentOnScreen * 0xFF));
}
android.util.Log.i("Slider", "onDraw(" + percentOnScreen + ") -> alpha(" + shadowPaint.getAlpha() + ')');
canvas.drawRect(shift, 0, screenWidth, screenHeight, shadowPaint);
super.onDraw(canvas);
}
#SuppressWarnings("unused")
public float getPercentOnScreen() {
return percentOnScreen;
}
/** Repeatedly invoked by an Animator. */
#SuppressWarnings("unused")
public void setPercentOnScreen(float fraction) {
this.percentOnScreen = fraction;
shift = (int)(fraction < 1.0 ? fraction * screenWidth : screenWidth);
setX(-shift);
android.util.Log.i("Slider", "setPOS(" + fraction + ") -> invalidate(" + shift + ',' + screenWidth + ')');
invalidate(shift, 0, screenWidth, screenHeight);
//invalidate() // Makes no difference
}
}
What's weird is that this violates symmetry. I'm doing the exact same thing in reverse when sliding out as I do when sliding in, but the behaviour is different. I'm probably overlooking something stupid. Any ideas?
It's a bug in the invalidation/redrawing logic for hardware-accelerated views in Android. I would expect to see the same bug in JB by default, but only on ICS if you opt into hardware acceleration (hw accel is enabled by default as of JB).
The problem is that when views are removed from the hierarchy and then animated out (such as what happens in the fragment transaction in your app, or in LayoutTransition when a removed view is faded out, or in an AlphaAnimation that fades out a removed view), they do not participate in the same invalidation/redrawing logic that normal/parented child views do. They are redisplayed correctly (thus we see the fragment slide out), but they are not redrawn, so that if their contents actually change during this period, they will not be redisplayed with those changes. The effect of the bug in your app is that the fragment slides out correctly, but the shadow is not drawn because that requires the view to be redrawn to pick up those changes.
The way that you are invalidating the view is correct, but the bug means that the invalidation has no effect.
The bug hasn't appeared before because, I believe, it's not common for disappearing views to change their appearance as they are being animated out (they usually just slide or fade out, which works fine).
The bug should be fixed in a future release (I have a fix for it already). Meanwhile, a workaround for your particular situation is to add an invalidation of the parent container as well; this will force the view to be redrawn and to correctly display the shadow:
if (getParent() instanceof ViewGroup) {
((ViewGroup) getParent()).invalidate();
}

Categories

Resources