I try to hide a picture and show interactively its portions when user touches a screen. I tried numerous approaches like having background view that is overlapsed with views that I will make transparent which somehow worked. A final solution shall be a single custom view which will give me more painting flexibility.
Activity:
hiddenPicture.setBackgroundResource(R.drawable.picture);
View:
init()
eraserPaint = new Paint();
eraserPaint.setColor(Color.TRANSPARENT);
// eraserPaint.setAlpha(0);
eraserPaint.setXfermode(new PorterDuffXfermode(PorterDuff.Mode.CLEAR));
veilPaint = new Paint();
veilPaint.setColor(Color.GRAY);
protected void onDraw(Canvas canvas) {
super.onDraw(canvas);
canvas.drawRect(0, 0, w, h, veilPaint);
canvas.drawOval(new RectF(20, 20, 220, 220), eraserPaint);
}
The problem is that the oval is black. I found tons of similar questions but I am too junior in Android to apply them to my case. What shall I do to erase oval in grey veil and show background picture? Thank you.
Update:
I found nice blog: http://www.41post.com/4794/programming/android-rendering-a-path-with-a-bitmap-fill
I recycled it to my code:
fillBMP = BitmapFactory.decodeResource(context.getResources(), R.drawable.picture);
fillBmpShader = new BitmapShader(fillBMP, Shader.TileMode.CLAMP, Shader.TileMode.CLAMP);
eraserPaint.setColor(0xFFFFFFFF);
eraserPaint.setStyle(Paint.Style.FILL);
eraserPaint.setShader(fillBmpShader);
It seemed to work but the problem is that it does not scale the image used as brush. Is really the only solution to implement onDraw() this way:
Paint the picture
Paint the veil except uncovered parts
I worry about the performance. I do not want to paint complete screen after each user interaction. I would prefer to repaint only relevant parts. Is this even possible or am I over-optimizing already?
I have to maintain and paint two bitmaps - one for background and second for veil with transparent hollows:
private void setupDrawing() {
eraserPaint = new Paint();
eraserPaint.setColor(Color.TRANSPARENT);
eraserPaint.setXfermode(new PorterDuffXfermode(PorterDuff.Mode.CLEAR));
veilPaint = new Paint();
veilPaint.setColor(Color.GREEN);
canvasPaint = new Paint(Paint.DITHER_FLAG);
}
// http://developer.android.com/training/custom-views/custom-drawing.html
#Override
protected void onSizeChanged(int w, int h, int oldw, int oldh) {
super.onSizeChanged(w, h, oldw, oldh);
canvasBitmap = Bitmap.createBitmap(w, h, Bitmap.Config.ARGB_8888);
drawCanvas = new Canvas(canvasBitmap);
drawCanvas.drawRect(0, 0, w, h, veilPaint);
}
protected void onDraw(Canvas canvas) {
canvas.drawBitmap(fillBitmap, 0, 0, canvasPaint);
canvas.drawBitmap(canvasBitmap, 0, 0, canvasPaint);
}
public boolean onTouchEvent(MotionEvent event) {
if (event.getAction() == MotionEvent.ACTION_MOVE) {
drawCanvas.drawCircle(event.getX(), event.getY(), 150, eraserPaint);
invalidate();
}
return true;
}
Related
I am trying to make ComposeShader using LinearGradients but may have few problem.
1) If I am only use one LinearGradient like
lgA = new LinearGradient(0, 0, 0, h, 0xff000000, 0xffffffff, Shader.TileMode.CLAMP);
then above part of the requirement correct
2) If use ComposeShader then result not like actual requirement
Actual Requirment :
Then how I can solve this problem please any one help me for this problem.
public class DrawGradient extends View {
Paint paint;
LinearGradient lgA;
LinearGradient lgB;
ComposeShader shader;
public DrawGradient(Context context) {
super(context);
initView();
}
private void initView() {
setLayerType(View.LAYER_TYPE_SOFTWARE, null);
paint = new Paint();
paint.setAntiAlias(true);
paint.setFilterBitmap(true);
}
#Override
protected void onSizeChanged(int w, int h, int oldw, int oldh) {
super.onSizeChanged(w, h, oldw, oldh);
lgA = new LinearGradient(0, 0, w, h, 0xff000000, 0xffffffff, Shader.TileMode.CLAMP);
lgB = new LinearGradient(0, h, w, 0, 0xffffffff, 0xff000000, Shader.TileMode.CLAMP);
shader = new ComposeShader(lgA, lgB, PorterDuff.Mode.MULTIPLY);
}
#Override
protected void onDraw(Canvas canvas) {
super.onDraw(canvas);
paint.setShader(shader);
canvas.drawPaint(paint);
}
}
Code Output :
Since this four months I imagine this will be of little help now, but here it is for anyone else with a similar problem.
The problem lies in these two lines:
lgA = new LinearGradient(0, 0, w, h, 0xff000000, 0xffffffff, Shader.TileMode.CLAMP);
lgB = new LinearGradient(0, h, w, 0, 0xffffffff, 0xff000000, Shader.TileMode.CLAMP);
You've created two linear gradients; one going from top-left to bottom-left, the other going from bottom-left to top-right. What you are looking for is this:
lgA = new LinearGradient(0, 0, 0, h/2, Color.BLACK, Color.WHITE, Shader.TileMode.CLAMP);
lgB = new LinearGradient(0, h/2, 0, h, Color.WHITE, Color.BLACK, Shader.TileMode.CLAMP);
With these changes, it worked for me. It's important to note that ComposeShader wouldn't work without the line:
setLayerType(View.LAYER_TYPE_SOFTWARE, null);
For reasons explained here.
ComposeShader seems to be mostly used for overlaying Shaders, such as LinearGradients, rather than creating a three colour transition which you're looking for. Alternatively you could have used LinearGradient by itself, but with the other constructor, which can take more than two different colours. E.g.
int[] colors = {Color.BLACK,Color.WHITE,Color.BLACK};
float[] pos = {0.0f,0.5f,1.0f};
paint.setShader(new LinearGradient(0,0,0,h,colors,pos,Shader.TileMode.CLAMP));
This would have given you the same effect, without needing ComposeShader. However I was using this method in my own project, but using Color.TRANSPARENT in the gradient and it was coming out black rather than transparent.
I created a function to draw a text on a path:
public void drawText(float x, float y, String text) {
Log.i("DRAWING", "drawText");Typeface.BOLD);
mPath.reset();
mPath.moveTo(x, y);
mPath.lineTo(x+200,y);
Paint textPaint = new Paint();
textPaint.setColor(Color.RED);
textPaint.setTextSize(20f);
textPaint.setAntiAlias(true);
textPaint.setStrokeWidth(5f);
textPaint.setStyle(Paint.Style.FILL);
mCanvas.drawTextOnPath(text, mPath, 0, 0, textPaint);
setLayerType(View.LAYER_TYPE_SOFTWARE, null);
paths.add(mPath);
invalidate();
}
I set a Bitmap like this:
#Override
protected void onSizeChanged(int w, int h, int oldw, int oldh)
{
mBitmap = Bitmap.createBitmap(w, h, Bitmap.Config.ARGB_8888);
mCanvas = new Canvas();
mCanvas.setBitmap(mBitmap);
reset();
}
Question:
When I set a bitmap, it works fine and the text appears on the ImageView, but when I don't, just a white line appears and not the text.
Do I have to use a Bitmap to draw text on path with drawTextOnPath ? Because I want to use only paths (all works fine except text, like it needed a Bitmap).
So a Bitmap is mandatory to draw text with drawTextOnPath.
The text won't appear if there is no Bitmap.
For those who, like me, implemented a Undo/Redo functionnality, I guess you have to save both paths and bitmaps to allow user to go back and forward.
During the past weeks I was looking for appropriate solution for zooming and scrolling functionality on a custom view.In my custom view I am drawing bitmaps on my canvas in onDraw() and creating new bitmap and canvas in onSizeChanged().
my code snippet here :
onDraw() method.In this method I am calling drawBitmap() this method takes image times from pagesProvider class that is my custom class.
Bitmap backBitmap = pagesProvider.getPageBitmap(tile);
src = new Rect();
dst = new Rect();
#Override
public void onDraw(Canvas canvas) {
drawBitmap(canvas, b, src, dst);
}
Here it is drawBitmap() this method drawBitmap on canvas based on Color mode. This color takes from my custom Options Class.And again I am drawing bitmap on canvas with mBitmap this mBitmap created on onSizeChanged() and drawing path on canvas.
private void drawBitmap(Canvas canvas, Bitmap backBitmap, Rect src, Rect dst) {
if (colorMode != Options.COLOR_MODE_NORMAL) {
paint.setColorFilter(new ColorMatrixColorFilter(new ColorMatrix(
Options.getColorModeMatrix(this.colorMode))));
canvas.drawBitmap(backBitmap, src, dst, paint);
} else {
canvas.drawBitmap(b, src, dst, null);
}
canvas.drawBitmap(mTopBitmap, 0, 0, mPaint); // mTopBitmap canvas is creating new bitmap on backBitmap bitmap canvas.
canvas.drawPath(mPath, mPaint);
}
onSizeChanged() :
#Override
protected void onSizeChanged(int w, int h, int oldw, int oldh) {
super.onSizeChanged(w, h, oldw, oldh);
this.width = w;
this.height = h;
mTopBitmap = Bitmap.createBitmap(w, h, Bitmap.Config.ARGB_8888);
mCanvas = new Canvas(mTopBitmap);
}
If I remove canvas.drawPath(mPath, mPaint); line from drawBitmap() canvas is not drawing paths on mTopBitmap canvas.And if I remove canvas.drawBitmap(mTopBitmap, 0, 0, mPaint); paths are drawn on backBitmap but those paths resetting when touch up.
In onTouch UP I am calling mCanvas.drawPath(mPath, mPaint); for drawing new path on mTopBitmap.
And My problem was I want to scroll the mTopBitmap canvas content whatever I draw on mCanvas and zoom the mTopBitmap canvas content of mCanvas.
I written zooming functionality and I added scroller according to backBitmap size.It's working fine.But I have confuse with mTopBitmap canvas how I can put scroll and zoom into mTop Canvas.
I need to display a TextView over a gradient background. The TextView itself should have a plain white background, and the text should be transparent.
However, setting a transparent color (#00000000) to the text doesn't work: it only shows a white rectangle, the background doesn't show up where the text is (the text takes the same color as the TextView background).
How can I display a transparent text with a background color on my TextView?
Update, Jan 30, 2016
I made a small library and written a blog post out of this answer, so you don't need to copy and paste code and I do the maintenance for you. :)
Use the view in xml as:
<it.gilvegliach.android.transparenttexttextview.TransparentTextTextView
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:background="#drawable/view_bg"
android:text="Hello World" />
Gradle dependency:
compile 'it.gilvegliach.android:transparent-text-textview:1.0.3'
Original Answer
This is how you can achieve that effect:
you render the text over a transparent background on a bitmap
you use that bitmap to clip the text shape out of the solid white background
Here is a simple subclass of TextView that does that.
final public class SeeThroughTextView extends TextView
{
Bitmap mMaskBitmap;
Canvas mMaskCanvas;
Paint mPaint;
Drawable mBackground;
Bitmap mBackgroundBitmap;
Canvas mBackgroundCanvas;
boolean mSetBoundsOnSizeAvailable = false;
public SeeThroughTextView(Context context)
{
super(context);
mPaint = new Paint();
mPaint.setXfermode(new PorterDuffXfermode(Mode.DST_OUT));
super.setTextColor(Color.BLACK);
super.setBackgroundDrawable(new ColorDrawable(Color.TRANSPARENT));
}
#Override
#Deprecated
public void setBackgroundDrawable(Drawable bg)
{
mBackground = bg;
int w = bg.getIntrinsicWidth();
int h = bg.getIntrinsicHeight();
// Drawable has no dimensions, retrieve View's dimensions
if (w == -1 || h == -1)
{
w = getWidth();
h = getHeight();
}
// Layout has not run
if (w == 0 || h == 0)
{
mSetBoundsOnSizeAvailable = true;
return;
}
mBackground.setBounds(0, 0, w, h);
invalidate();
}
#Override
public void setBackgroundColor(int color)
{
setBackgroundDrawable(new ColorDrawable(color));
}
#Override
protected void onSizeChanged(int w, int h, int oldw, int oldh)
{
super.onSizeChanged(w, h, oldw, oldh);
mBackgroundBitmap = Bitmap.createBitmap(w, h, Bitmap.Config.ARGB_8888);
mBackgroundCanvas = new Canvas(mBackgroundBitmap);
mMaskBitmap = Bitmap.createBitmap(w, h, Bitmap.Config.ARGB_8888);
mMaskCanvas = new Canvas(mMaskBitmap);
if (mSetBoundsOnSizeAvailable)
{
mBackground.setBounds(0, 0, w, h);
mSetBoundsOnSizeAvailable = false;
}
}
#Override
protected void onDraw(Canvas canvas)
{
// Draw background
mBackground.draw(mBackgroundCanvas);
// Draw mask
mMaskCanvas.drawColor(Color.BLACK, PorterDuff.Mode.CLEAR);
super.onDraw(mMaskCanvas);
mBackgroundCanvas.drawBitmap(mMaskBitmap, 0.f, 0.f, mPaint);
canvas.drawBitmap(mBackgroundBitmap, 0.f, 0.f, null);
}
}
Example screenshot: indigo pattern for activity background, pink solid fill for TextView background.
This works both for solid color backgrounds and general drawables. Anyway, this is only a BASIC implementation, some feature such as tiling are not supported.
I have not tried this, but you might be able to do this by (against all documentation advice) getting the TextPaint through TextView.getTextPaint() and call setXferMode(new PorterDuffXferMode(PorterDuff.Mode.MULTIPLY)), in order to clear the alpha bits on the background while rendering.
Otherwise, implement your own text view where you are in full control of the rendering.
Add this code to your textview tag:
android:background="#07000000"
I need a little help with the application that I'm working. I'm trying to create an application for painting and there is one problem which I noticed a few days ago and now I decide to make some research to solve it. When I use PorterDuff.Mode.CLEAR to use my brush as eraser it's working as it should be while my background is white. But if I set my background in other color ( for example Black like : mCanvas.drawColor(Color.BLACK); ) and after I use the eraser, the places where I use the eraser are painted with white color.
Here is how I'm setting the variables for my eraser :
erase.setOnClickListener(new View.OnClickListener() {
#Override
public void onClick(View v) {
stamp=false;
MODE = ERASER_MODE;
mPaint.setColorFilter(null);
mPaint.setShader(null);
mPaint.setMaskFilter(null);
mPaint.setXfermode(new PorterDuffXfermode(PorterDuff.Mode.CLEAR));
}
});
So any ideas why it's happening and how can I fix that problem?
Thanks in advance!
Actually I find where were my problem. In my Custom View that I was using for drawing and etc I was doing this
#Override
protected void onSizeChanged(int w, int h, int oldw, int oldh) {
super.onSizeChanged(w, h, oldw, oldh);
mBitmap = Bitmap.createBitmap(w, h, Bitmap.Config.ARGB_8888);
mCanvas = new Canvas(mBitmap);
mCanvas.drawColor(parentColor); //parentColor = currentBackgroundColor
}
#Override
protected void onDraw(Canvas canvas) {
canvas.drawColor(Color.WHITE);
canvas.drawBitmap(mBitmap, 0, 0, mBitmapPaint);
canvas.drawPath(mPath, mPaint);
}
and I had to change only on my