Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

How to overlay list item text color in specific region?

Let suppose we have the custom ListView which extends onDraw() method by drawing some rectangle in it.

class Listpicker extends ListView {
//..Some other methods here..//
    @Override
    protected void onDraw(android.graphics.Canvas canvas) {
    super.onDraw(canvas);
    canvas.drawBitmap(glassPickerBitmap, null, glassRect, semiTransparrentPaint);
    }
}

The bitmap drawn represent some sort of glass which is rectangle with width equal to the width of listview and some height. What I want is when list item scrolls into this rectangle the color used for drawing text (not all item with background and etc.) would be changed into some another color. So, for example, when list item fits so that only half height of text fits into glassPickerBitmap, the outside part of list item should remain in its ordinal colors, and the part that is inside glassPickerBitmap should change its text color. List items are simple TextViews without any background.

How can be this achieved? Maybe any ColorFilters assigned to the Paint? But where? onDraw() method of Listview is not even called when ListView is scrolled... Can be this done inside customized views, for example, that will be ListView's items? Or may be some Color/Shader/do not know what else overlay can be used here, but where?

EDIT (added image examples):

Here is example of some crop from real-life app. There are 2 ListViews: one on the left side, other on the right. Glass is the grayed rectangle. Now, left list has "United States Dollar" currency selected. And I am scrolling right ListView in that way, that selection is somewhere between USD and Afghan Afghani. Now, what I want, is that USD currency in the left ListView would be drawn in red (exact color doesn't matter now, I will change it later to something meaningful) AND, in the same time, bottom part of "United States Dollar" and top part of "Afghan Afghani" in the right ListView would be drawn also in the same red color.

This color changing should be done in dynamic way - color should be changed only for the part of text that is under glass rectangle during scrolling of the list.

*OK, EUR and USD here are special currencies drawn with not standard cyan color. The question is about at least text with white color. But if it will be possible to change also cyan color it would be great.

List example

like image 250
Prizoff Avatar asked Jul 05 '13 16:07

Prizoff


2 Answers

As you can tell in their implementation they use and update a fixed position in their ListAdapter so then they update the View that matches accordingly, which is to me the easiest and simpler way to do it, but you don't get the half-half colored text, which I think you still want :)


http://i.imgur.com/jHvuBcL.png

So here is probably one of the worst answers I've given, sorry about it, I'll probably revisit this or hope someone can point you to a better solution

You can see a blurry demo at:

http://telly.com/Y98DS5

The dirty way:

  • Create a Paint with a proper ColorMatrixColorFilter, for the purposes of answering this I'm just desaturating, you'll have to figure out your 5x4 matrix for ColorMatrix to get the cyan your looking for. Also you will need some Rects to define source and dest when drawing below.
  • Create an off screen Bitmap and Canvas which you'll use to draw your list, that's done in onSizeChanged so we have proper view layout values, here you can start noticing the dirtiness as you will have to allocate a Bitmap as big as your ListView so you may get OutOfMemoryException.
  • Override draw to use your off screen Canvas and let ListView draw to it, then you can draw the Bitmap to the given Canvas that will draw your list as usual, then draw just the part of that Bitmap which will be drawn differently using the Paint we defined before, you will get overdraw for those pixels.
  • Finally draw your pre-fabricated glass Bitmap, I would recommend a n-patch (9-patch) so it scales and looks sharp across screens and densities, then you get more overdraw for those pixels.

The whole cake looks like:

public class OverlayView extends ListView {
  private RectF mRect;
  private Rect mSrcRect;
  private Paint mPaint;

  private Canvas mCanvas;
  private Bitmap mBitmap;
  private float mY;
  private float mItemHeight;

  public OverlayView(Context context) {
    super(context);

    initOverlay();
  }

  public OverlayView(Context context, AttributeSet attrs) {
    super(context, attrs);

    initOverlay();
  }

  public OverlayView(Context context, AttributeSet attrs, int defStyle) {
    super(context, attrs, defStyle);

    initOverlay();
  }

  private void initOverlay() {
    // DO NOT DO THIS use attrs
    final Resources res = getResources();
    mY = res.getDimension(R.dimen.overlay_y);
    mItemHeight = res.getDimension(R.dimen.item_height);
    //
    mRect = new RectF();
    mSrcRect = new Rect();
    mPaint = new Paint();

    ColorMatrix colorMatrix = new ColorMatrix();
    colorMatrix.setSaturation(0);
    mPaint.setColorFilter( new ColorMatrixColorFilter(colorMatrix) );
  }

  @Override
  public void draw(Canvas canvas) {
    super.draw(mCanvas);
    canvas.drawBitmap(mBitmap, 0, 0, null);

    canvas.drawBitmap(mBitmap, mSrcRect, mRect, mPaint);
  }

  @Override
  protected void onSizeChanged(int w, int h, int oldw, int oldh) {
    super.onSizeChanged(w, h, oldw, oldh);

    mRect.set(0, mY, w, mY + mItemHeight);
    mRect.roundOut(mSrcRect);

    mBitmap = Bitmap.createBitmap(w, h, Bitmap.Config.ARGB_8888);
    mCanvas = new Canvas(mBitmap);
  }
}

So that said, I hope you end up with a cleaner solution, even if you don't get that half-half sexiness :)

like image 67
eveliotc Avatar answered Nov 07 '22 10:11

eveliotc


First, keep a spare bitmap and canvas:

class Listpicker extends ListView {
    Bitmap bitmapForOnDraw = null;
    Canvas canvasForOnDraw = null;

Make sure it's the right size:

    @override
    protected void onSizeChanged (int w, int h, int oldw, int oldh) {
        if (bitmapForOnDraw != null) bitmapForOnDraw.recycle();
        bitmapForOnDraw = Bitmap.createBitmap(w, h, Bitmap.Config.ARGB_8888);
        canvasForOnDraw = new Canvas(bitmapForOnDraw);
    }

And when we come to drawing the list:

    @override
    protected void onDraw(android.graphics.Canvas realCanvas) {
        // Put the list in place
        super.onDraw(realCanvas);

        // Now get the same again
        canvasForOnDraw.drawColor(0x00000000, PorterDuff.Mode.CLEAR);
        super.onDraw(canvasForOnDraw);
        // But now change the colour of the white text
        canvasForOnDraw.drawColor(0xffff00ff, PorterDuff.Mode.MULTIPLY);

        // Copy the different colour text into the right place
        canvas.drawBitmap(bitmapForOnDraw, glassRect, glassRect overwritePaint);

        // finally, put the box on.
        canvas.drawBitmap(glassPickerBitmap, null, glassRect, semiTransparrentPaint);
    }
like image 32
Neil Townsend Avatar answered Nov 07 '22 10:11

Neil Townsend