Thursday, May 12, 2011

Modifying (coloring, scaling) part of the text in the TextView

Thre are several ways to modify different parts of text in a single TextView. One of them is Html.fromHtm() that would format your text according to html tags you put in it beforehand. I find that approach too crude. You have to modify the original text manually to put opening and closing tags in, which can become really ugly if you need to do a lot of formatting.

The second way, is to use Spans and SpannableStringBuilder.
Spans give you ability to define modification, for example: new ForegroundColorSpan(Color.rgb(100, 100, 100)) can be used to change the color of the text. Similarly, new RelativeSizeSpan(0.8F) can be used to scale text.

SpannableStringBuilder allows us to apply those spans on particular areas of our text. Usage is very easy as demonstrated below:

private final static StyleSpan bss = new StyleSpan(android.graphics.Typeface.BOLD);
private final static ForegroundColorSpan fcs = new ForegroundColorSpan(Color.rgb(100, 100, 100));
private final static RelativeSizeSpan rszSmall = new RelativeSizeSpan(0.8F);
private final static RelativeSizeSpan rszBig = new RelativeSizeSpan(1.2F);

private void insertFormattedText(final TextView view, final String text) {
  final SpannableStringBuilder sb = new SpannableStringBuilder(text);  

  // Make characters from 0 to 2 bold  
  sb.setSpan(bss, 0, 2, Spannable.SPAN_INCLUSIVE_INCLUSIVE);  
  // Change the color of characters from 2 to 4
  sb.setSpan(fcs, 2, 4, Spannable.SPAN_INCLUSIVE_INCLUSIVE);   

  // Scale down characters from 4 to 6
  sb.setSpan(rszSmall, 4, 6, Spannable.SPAN_INCLUSIVE_INCLUSIVE);   

  // Make characters from 6 to 8 bold
  sb.setSpan(bss, 6, 8, Spannable.SPAN_INCLUSIVE_INCLUSIVE);   

  // Scale up characters from 8 to 12
  sb.setSpan(rszBig, 8, 12, Spannable.SPAN_INCLUSIVE_INCLUSIVE);   

  // Set the text into the TextView
  view.setText(sb);
}

Spannable.SPAN_INCLUSIVE_INCLUSIVE points that both (ending and starting) characters must be included in formatting.

Just look into android.text.style.* package for a list of available spans ;)

Wednesday, May 11, 2011

Adding a fling gesture listener to a view

Here's an example of a simple fling gesture listener, that you can add to almost any view, like ImageView to browse photos and images by flicking, or even to layout to change user interface on fling.

First, lets' start with the most simplest version:

public class MyActivity extends Activity {
  private void onCreate() {
     // Set your layout
     final ImageView imageView = (ImageView) findViewById(R.id.image_view);
     imageView.setOnTouchListener(new OnTouchListener() {
        @Override
        public boolean onTouch(final View view, final MotionEvent event) {
           return gdt.onTouchEvent(event);
        }
     });
  }

  private final GestureDetector gdt = new GestureDetector(new GestureListener());
 
 
  private class GestureListener extends SimpleOnGestureListener {

     private final int SWIPE_MIN_DISTANCE = 120;
     private final int SWIPE_THRESHOLD_VELOCITY = 200;
 
     @Override
     public boolean onFling(MotionEvent e1, MotionEvent e2, float velocityX, float velocityY) {
        if(e1.getX() - e2.getX() > SWIPE_MIN_DISTANCE && Math.abs(velocityX) > SWIPE_THRESHOLD_VELOCITY) {
           // Right to left, your code here
           return true;
        } else if (e2.getX() - e1.getX() > SWIPE_MIN_DISTANCE && Math.abs(velocityX) >  SWIPE_THRESHOLD_VELOCITY) {
           // Left to right, your code here
           return true;
        }
        if(e1.getY() - e2.getY() > SWIPE_MIN_DISTANCE && Math.abs(velocityY) > SWIPE_THRESHOLD_VELOCITY) {
           // Bottom to top, your code here
           return true;
        } else if (e2.getY() - e1.getY() > SWIPE_MIN_DISTANCE && Math.abs(velocityY) > SWIPE_THRESHOLD_VELOCITY) {
           // Top to bottom, your code here
           return true;
        }
        return false;
     }
  }
}



So here's the example of an activity, that somewhere on it's layout has an ImageView, that we want user to be able to fling.
What we basically do, is recording when and where user has touched the view, and where and when released, so we can calculate how fast and at what distance has user flinged. That's what SWIPE_THRESHOLD_VELOCITY and SWIPE_MIN_DISTANCE fields are for. You can set your own values, of course. The ones I gave are the optimal IMO.

This example is good, but what if we want to be able to detect flings on other views in other activities? Copying the code for every activity would be annoying and too difficult to support. Not even speaking about the ugly way we need to run some code on a specific fling. Here's a version of a "really easy to use" listener, that can be used anywhere without much trouble:

OnFlingGestureListener.java


public abstract class OnFlingGestureListener implements OnTouchListener {

  private final GestureDetector gdt = new GestureDetector(new GestureListener());

  @Override
  public boolean onTouch(final View v, final MotionEvent event) {
     return gdt.onTouchEvent(event);
  }

  private final class GestureListener extends SimpleOnGestureListener {

     private static final int SWIPE_MIN_DISTANCE = 60;
     private static final int SWIPE_THRESHOLD_VELOCITY = 100;

     @Override
     public boolean onFling(MotionEvent e1, MotionEvent e2, float velocityX, float velocityY) {
        if(e1.getX() - e2.getX() > SWIPE_MIN_DISTANCE && Math.abs(velocityX) > SWIPE_THRESHOLD_VELOCITY) {
           onRightToLeft();
           return true;
        } else if (e2.getX() - e1.getX() > SWIPE_MIN_DISTANCE && Math.abs(velocityX) > SWIPE_THRESHOLD_VELOCITY) {
           onLeftToRight();
           return true;
        }
        if(e1.getY() - e2.getY() > SWIPE_MIN_DISTANCE && Math.abs(velocityY) > SWIPE_THRESHOLD_VELOCITY) {
           onBottomToTop();
           return true;
        } else if (e2.getY() - e1.getY() > SWIPE_MIN_DISTANCE && Math.abs(velocityY) > SWIPE_THRESHOLD_VELOCITY) {
           onTopToBottom();
           return true;
        }
        return false;
     }
  }

  public abstract void onRightToLeft();

  public abstract void onLeftToRight();

  public abstract void onBottomToTop();

  public abstract void onTopToBottom();

}


And previous example with the new listener:


public class MyActivity extends Activity {

  private void onCreate() {
     // Set your layout
     final ImageView imageView = (ImageView) findViewById(R.id.image_view);
     imageView.setOnTouchListener(new OnFlingGestureListener() {

        @Override
        public void onTopToBottom() {
           //Your code here
        }

        @Override
        public void onRightToLeft() {
           //Your code here
        }

        @Override
        public void onLeftToRight() {
           //Your code here
        }

        @Override
        public void onBottomToTop() {
           //Your code here
        }
     });
  }
}


As easy as that.

P.S. Criticism and comments are welcomed.