4

I want to achieve something like this:

enter image description here

The first thing that comes to mind is to draw the text on a canvas two times and cover the first text with a shape. But maybe there is a better solution.

Community
  • 1
  • 1
Ivan Fork
  • 812
  • 1
  • 9
  • 22
  • Is this supposed to be a progress bar? Or just the number is highlighted? – JoxTraex Aug 08 '16 at 16:22
  • 2
    Sounds like what you want is `paint.setXfermode(new PorterDuffXfermode(PorterDuff.Mode.XOR));` See this question for more info: http://stackoverflow.com/questions/8280027/what-does-porterduff-mode-mean-in-android-graphics-what-does-it-do – kenny_k Aug 08 '16 at 16:23

2 Answers2

3

One approach is using a PorterDuffXfermode for compositing the blue rectangle over the text. You could extend TextView and override onDraw() to draw the rectangle after the text has been drawn, and with the proper mode (I believe XOR is the one you want) it should achieve the desired effect. Something like this:

public class ProgressTextView extends TextView {

    private static final float MAX_PROGRESS = ...;

    private Paint mPaint;

    public ProgressTextView(Context context) {
        super(context);
        initPaint();
    }

    /* other constructor omitted, but do the same pattern in those */

    private void initPaint() {
        mPaint = new Paint();
        mPaint.setColor(...);
        mPaint.setXfermode(new PorterDuffXfermode(Mode.XOR));
        // note: you may also need the following line if hardware accel is available
        setLayerType(LAYER_TYPE_SOFTWARE, null);
    }

    @Override
    public void onDraw(Canvas canvas) {
        super.onDraw(canvas);
        drawProgress(canvas);
    }

    private void drawProgress(Canvas canvas) {
        int w = getWidth() - getPaddingLeft() - getPaddingRight();
        int h = getHeight() - getPaddingTop() - getPaddingBottom();
        float progress = getProgress();
        float rectW = w * (progress / MAX_PROGRESS);

        int saveCount = canvas.save();
        canvas.translate(getPaddingLeft(), getPaddingTop());
        canvas.drawRect(0, 0, rectW, h, mPaint);
        canvas.restoreToCount(saveCount);
    }

    private float getProgress() {
        // TODO
    }
}

More info on Porter/Duff compositing: http://ssp.impulsetrain.com/porterduff.html

Karakuri
  • 38,365
  • 12
  • 84
  • 104
  • Note: I started writing this answer before the comment made by @theFunkyEngineer, but the post he linked to is useful as well. – Karakuri Aug 08 '16 at 16:46
  • This looks like a right direction but when I use Mode.XOR it removes both the rect and the text, I've tried other modes but with no luck. – Ivan Fork Aug 09 '16 at 05:36
  • @IvanFork try using the debugger and make sure the drawing is happening in the correct coordinates. Maybe remove the `Xfermode` on the `Paint` just to see if it's drawing in the right place. Also, there's a commented line in there that calls `setLayerType()` on the view, which you may need to uncomment to make it work. – Karakuri Aug 09 '16 at 06:05
  • Did you tested your code? If it's working can you provide xml? I've tried multiple approaches using all PorterDuff modes and sadly none of them worked, usually the text get completely cut along with the rect. I ended up using canvas.clipRect and drawing the second half of the text again. – Ivan Fork Aug 10 '16 at 10:57
  • @IvanFork I had to uncomment the `setLayerType()` call, but otherwise it worked exactly as I expected it to using `XOR`. – Karakuri Aug 10 '16 at 15:29
  • I was testing this only in the xml preview, now I'm running it on the device and everything is working as expected. Strangely some PorterDuff modes are working in the xml preview and some don't, this is what confused me. Thank you! – Ivan Fork Aug 10 '16 at 16:43
  • @IvanFork Yeah, you can't always trust the layout editor preview ;) – Karakuri Aug 10 '16 at 16:50
2

Your approach sounds good for a pure android solution, however I am going to go a little unconventional here and suggest that you make that part of your UI into a HTML component web-view and go with html-css solution as this kind of rendering is a lot easier to do. Not sure if this is idea solution for Purists, but it certainly sounds easily doable. Check this working example where I tried to re-create your UI (Just hit run code snippet, or check the jsfiddle link at the end):

function barWidth() {
    var barWidth = $('.progress-bar').width();
    $('.progress-fill-text').css('width',barWidth);
}
barWidth();
window.onresize = function() {
    barWidth();
}
.progress-bar {
    background:#ccc;
    color: #7d7d7d;
    padding:10px 0;
    width:100%;
    text-align:center;
    position:relative;
    font-size: 40px;
    font-family: 'Open Sans', sans-serif;
}

.progress-fill {
    background:#0095dd;
    color:#fff;
    width:47%;
    padding:10px 0;
    position:absolute;
    left:0;
    top:0;
    overflow:hidden;
}

.progress-fill-text {
}
<script src="https://ajax.googleapis.com/ajax/libs/jquery/2.1.1/jquery.min.js"></script>
<link href='https://fonts.googleapis.com/css?family=Open+Sans' rel='stylesheet' type='text/css'>

<div class="progress-bar">
    <div class="progress-fill">
        <div class="progress-fill-text">37 UNITS</div>
    </div>
    37 UNITS
</div>

http://jsfiddle.net/zkL29/56/

Shaunak
  • 17,377
  • 5
  • 53
  • 84
  • Maybe ideal in a web environment, but not when you need to control the content via a view structure. – JoxTraex Aug 08 '16 at 16:21
  • You can certainly control the web-view from your Java Code even if you go with a web view. consider this, all you have to do is change the `width` param in the css for the progress bar to move. Now if you write little more JavaScript , to read the width from URL, from your Android code you can keep updating the javascript route of the web view, and you have a dynamic component. – Shaunak Aug 08 '16 at 16:24
  • Yes you could, but the main problem is that it seems overkill for something so simple. Also consider, lets say there was supposed to be an animation on how the content would need to be rendered/animated, unless you're a web expert, making that animation may be more work by having focused on the web component. I'm not say it can't be done, i'm just saying that its more of a maintenance cost to leverage something like this for an app (in android) that may not even be web oriented in any way. – JoxTraex Aug 08 '16 at 16:28
  • 1
    Agree on all counts with you. It was just an alternative I suggested. I guess it comes down to the cost of how complex native rendering is to write and maintain against implementing a html component. I would be curious to see if there other solutions. – Shaunak Aug 08 '16 at 16:31
  • Either way +1 definitely a good way to do something like this if someone's app is web oriented :) – JoxTraex Aug 08 '16 at 16:31