Wednesday, October 30, 2013

Setting alpha (transparency), and flashing button animation example.

Setting transparency for a view in android 2.x version is not as easy as calling 1 method, but it still is pretty a trivial task:



final AlphaAnimation alphaAnimation = new AlphaAnimation(0.5F, 0.5F); // Creating new alpha animation, that makes the view half transparent

alphaAnimation.setDuration(0); // Make animation to perform instantly
alphaAnimation.setFillAfter(true); // Persist changes after the animation ends
yourLayout.startAnimation(alphaAnimation); // Call it for any view


As easy as that :)


final Animation animation = new AlphaAnimation(1, 0); // Change alpha from fully visible to invisible
animation.setDuration(250); // duration 250 milliseconds
animation.setInterpolator(new LinearInterpolator()); // do not alter animation rate
animation.setRepeatCount(Animation.INFINITE); // Repeat animation infinitely
animation.setRepeatMode(Animation.REVERSE); // Reverse animation at the end so the button will fade back in
final Button btn = (Button) findViewById(R.id.your_btn); // Your button
btn.startAnimation(animation);
btn.setOnClickListener(new OnClickListener() {
  @Override
  public void onClick(final View view) {
     // Some of your code on click
     view.clearAnimation()
  }
});


It's not limited for button only, you can se virtually for any view.

Friday, November 18, 2011

Raw screen coordinates (rawX & rawY) to ListView item position

Recently had to deal with this problem, which actually turned out pretty trivial to solve (thanks to riotopsys from stackoverflow.com).

Situation as an example: you have some sort of drag&drop functionality and you want to be able to drag some view onto an item in the ListView. My solution was to implement drag&drop through WindowsManager Layout, and position the view I'm dragging using rawX, rawY coordinates. But how do I check what element is below (if I'm hovering above a ListView)?

Here the solution:

// Since ListView is vertical, I only use Y coordinate
public final int getItemPositionFromRawYCoordinates(final int rawY) {
  // Ho many items are currently displayed on the screen
  final int total = listView.getLastVisiblePosition() - getFirstVisiblePosition();
  // We will keep item coordinates here while iterating
  final int[] coords = new int[2];
  for (int i=0; i<total; i++) {
     // Since ListView recycles rows, the amount of children != amount of items in the list. There will as many children as visible items currently on the screen. That's why we counted total
    final View child = listView.getChildAt(i);
     // Get current child position on screen (these are global coordinates)
    child.getLocationOnScreen(coords);
    // We need just the top coordinate
    final int top = coords[1];
     // And bottom
    final int bottom = top + child.getHeight();
     // If our touch Y coordinate is in between, that means we are currently pointing on that child;
    if ((rawY >= top) && (rawY <= bottom)) {
      return getFirstVisiblePosition()+i;
    }
  }
  return -1;
}


That way you get either -1 if no items are currently below your pointer (which is should be weird), or actual item position :)

Friday, September 16, 2011

Android soft (virtual) keyboard listener

If you use Evernote android app you might have noticed, that when, on a login screen, soft keyboard gets shown (to type password or username) the layout doesn't just scale, or scrolls. It changes (for example Evernotes logo is hidden).

Unfortunately, android api doesn't provide you with the specific tools to listen for a moment, when soft keyboard gets shown, or hidden. But there is a way (dirty hack) to assume that the keyboards state was changed.
We can guess it by listening measure changes in layout.
To do it, first, we need to make our layout to resize and scale instaed of scroll.  Add android:windowSoftInputMode="adjustResize" parameter to your activity in manifest.xml. This way, when soft keyboard is called, content will be resized instead of scrolled.

Next step is to actually listen for this resizing. And again, there is a catch. There is no such thing as OnMeasureChangedListener, so the only way is to extend the container layout and do it inside. As an example I extended LinearLayout, and this is what I've got:

public class MyLayout extends LinearLayout {

  public MyLayout(final Context context, final AttributeSet attrs) {
    super(context, attrs);
  }

  public MyLayout(Context context) {
    super(context);
  }

  private OnSoftKeyboardListener onSoftKeyboardListener;

  @Override
  protected void onMeasure(final int widthMeasureSpec, final int heightMeasureSpec) {
    if (onSoftKeyboardListener != null) {
      final int newSpec = MeasureSpec.getSize(heightMeasureSpec);
      final int oldSpec = getMeasuredHeight();
      // If layout became smaller, that means something forced it to resize. Probably soft keyboard :)
      if (oldSpec > newSpec){
        onSoftKeyboardListener.onShown();
      } else {
        onSoftKeyboardListener.onHidden();
      }
    }
    super.onMeasure(widthMeasureSpec, heightMeasureSpec);
  }

  public final void setOnSoftKeyboardListener(final OnSoftKeyboardListener listener) {
    this.onSoftKeyboardListener = listener;
  }

  // Simplest possible listener :)
  public interface OnSoftKeyboardListener {
    public void onShown();
    public void onHidden();
  }
}


That's about it. Of course you need to use your layout (MyLayout) in your code/xml for it to work. And example from activity:


((MyLayout)findViewById(R.id.layout)).setOnSoftKeyboardListener(new OnSoftKeyboardListener() {
  @Override
  public void onShown() {
    // Do something here
  }
  @Override
  public void onHidden() {
    // Do something here
  }
});

P.S. It works pretty well as it is. But you should check it for different situations. For example screen rotations, or other possible changes in the layout, that might trigger resize of any sort.  

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.