In this code, I would like to draw a line between the top of two ImageViews. However, when running the app, the custom view is shown solid black after calling invalidate().
Here is my code:
public class ArrowView extends RelativeLayout {
public Paint paint;
public Bitmap eraser;
public Canvas cacheCanvas;
public float leftX;
public float leftY;
public float rightX;
public float rightY;
public boolean update = false;
public ImageView iv_leftArrow;
public ImageView iv_rightArrow;
private int w;
private int h;
LayoutInflater mInflater;
public ArrowView(Context context) {
super(context);
this.setWillNotDraw(false);
mInflater = LayoutInflater.from(context);
init();
}
public ArrowView(Context context, AttributeSet attrs) {
super(context, attrs);
this.setWillNotDraw(false);
mInflater = LayoutInflater.from(context);
init();
}
public ArrowView(Context context, AttributeSet attrs, int defStyle) {
super(context, attrs, defStyle);
this.setWillNotDraw(false);
mInflater = LayoutInflater.from(context);
init();
}
#Override
public void onSizeChanged(int w, int h, int oldW, int oldH) {
this.w = w;
this.h = h;
super.onSizeChanged(w, h, oldW, oldH);
}
public void init() {
View v = mInflater.inflate(R.layout.arrow_view, this, true);
paint = new Paint(Paint.ANTI_ALIAS_FLAG);
paint.setColor(Color.BLACK);
paint.setStrokeWidth(5);
v.setBackgroundColor(Color.TRANSPARENT);
eraser = Bitmap.createBitmap(w, h, Bitmap.Config.ARGB_8888);
iv_leftArrow = (ImageView) v.findViewById(R.id.iv_leftarrow);
iv_rightArrow = (ImageView) v.findViewById(R.id.iv_rightArrow);
cacheCanvas = new Canvas();
cacheCanvas.setBitmap(eraser);
}
public void setCoordinates(float leftX, float leftY, float rightX, float rightY) {
this.leftX = leftX;
this.leftY = leftY;
this.rightX = rightX;
this.rightY = rightY;
}
#Override
public void onDraw(Canvas c) {
super.onDraw(c);
setCoordinates(iv_leftArrow.getX() + iv_leftArrow.getWidth() / 2, iv_leftArrow.getY(), iv_rightArrow.getX() + iv_rightArrow.getWidth() / 2, iv_rightArrow.getY() );
if (update) {
c.drawLine(leftX, leftY, rightX, rightY, paint);
update = false;
}
cacheCanvas.drawPath(p, paint);
}
}
Is there any reason why the custom view is showing as solid black after calling invalidate()?
Usually, the system handles resizing, hiding, showing and a ton of other things for your widgets automatically but it sometimes has issues if the underlying buffer for drawn pixels or backing data has changed or is stale (you swap the image resource on a View or the raw dataset changes). This occurs because there is no way that the OS can know that the data changed in the specific manner that it did.
In these cases where you are dealing with drawing, you have to tell the system that its underlying data is not in a good state with Widget.invalidate() and the re-drawing gets queued on the main thread just as you mentioned. Depending on the system implementation and Android version what is tracked for changes by the system varies but what I normally do is assume that system resources (byte arrays, char arrays, resource indexes, manual drawing on the context) are not tracked and need an invalidate and everything else will be handled by the system.
Source : When it's necessary to execute invalidate() on a View?
As you know, invalidate() is used to update a view by a call to onDraw.
What I think you were doing was calling it before setting update to true - so your onDraw method ran with a false update by the sounds of it.
Related
I want to make a circular suface view (porthole effect). Surface view is inside a Frame layout. I want to make a custom view that i can add to Frame layout on top of surface view and mask whole Frame layout to produce porthole effect so that surface view will be shown as circle.
I searched and a lot for answer on Web and Stackoverflow but failed.
Then i saw this question and i tried this custom view to mask frame layout(and hence surfaceview) but i am not getting the desired result.
What i want is a custom view that can take height and width of it's parent (parent is square in shape) and make a transparent circle at it's center touching all four sides at middle of the boundaries, rest(view - circle) of the view will be of color that i can set.
public class FocusView extends View {
private Paint mTransparentPaint;
private Paint mSemiBlackPaint;
private Path mPath = new Path();
public static float radius , xCor , yCor;
public FocusView(Context context) {
super(context);
initPaints();
}
public FocusView(Context context, AttributeSet attrs) {
super(context, attrs);
initPaints();
}
public FocusView(Context context, AttributeSet attrs, int defStyleAttr) {
super(context, attrs, defStyleAttr);
initPaints();
}
private void initPaints() {
mTransparentPaint = new Paint();
mTransparentPaint.setColor(Color.GREEN);
mTransparentPaint.setStrokeWidth(10);
mSemiBlackPaint = new Paint();
mSemiBlackPaint.setColor(Color.TRANSPARENT);
mSemiBlackPaint.setStrokeWidth(10);
}
#Override
protected void onDraw(Canvas canvas) {
super.onDraw(canvas);
mPath.reset();
mPath.addCircle(xCor,yCor,radius, Path.Direction.CW);
mPath.setFillType(Path.FillType.INVERSE_EVEN_ODD);
canvas.drawCircle(xCor,yCor,radius, mTransparentPaint);
canvas.drawPath(mPath, mSemiBlackPaint);
canvas.clipPath(mPath);
canvas.drawColor(Color.parseColor("#FFFFFF")); //A6000000
}
}
Please if somebody can help me. Thanks in advance.
This is an example of a view that paints the whole view pink and cuts a centered, circular hole making the parent visible:
public class FocusView extends View {
private Paint mCutPaint = new Paint(Paint.ANTI_ALIAS_FLAG);
private Bitmap mBitmap;
private Canvas mInternalCanvas;
public FocusView(Context context) {
super(context);
init();
}
public FocusView(Context context, AttributeSet attrs) {
super(context, attrs);
init();
}
public FocusView(Context context, AttributeSet attrs, int defStyle) {
super(context, attrs, defStyle);
init();
}
private void init() {
mCutPaint.setXfermode(new PorterDuffXfermode(PorterDuff.Mode.CLEAR));
}
#Override
protected void onSizeChanged(int w, int h, int oldw, int oldh) {
super.onSizeChanged(w, h, oldw, oldh);
if (mInternalCanvas != null) {
mInternalCanvas.setBitmap(null);
mInternalCanvas = null;
}
if (mBitmap != null) {
mBitmap.recycle();
mBitmap = null;
}
mBitmap = Bitmap.createBitmap(w, h, Bitmap.Config.ARGB_8888);
mInternalCanvas = new Canvas(mBitmap);
}
#Override
protected void onDraw(Canvas canvas) {
if (mInternalCanvas == null || mBitmap == null) {
return;
}
final int width = getWidth();
final int height = getHeight();
// make the radius as large as possible within the view bounds
final int radius = Math.min(width, height) / 2;
mInternalCanvas.drawColor(0xFFFF00FF);
mInternalCanvas.drawCircle(width / 2, height / 2, radius, mCutPaint);
canvas.drawBitmap(mBitmap, 0, 0, null);
}
}
The reason for drawing to an internal Bitmap first is that if you apply PorterDuff.Mode.CLEAR to the original Canvas it will cut away everything that's been previously drawn to the canvas, including the parent view.
There may be better solutions out there, but this one is simple enough to understand.
Im using the following code to animate the view when it is drawn...
public class MyView extends View {
int iCurStep = 0;// current animation step
class Points {
float x, y;
Points(float _x, float _y) {
x = _x;
y = _y;
}
}
Points[] drawPaths = {new Points(-75, 0), new Points(20, 60), new Points(60, 20)};
public MyView(Context context) {
super(context);
}
public MyView(Context context, AttributeSet attrs, int defStyleAttr) {
super(context, attrs, defStyleAttr);
}
public MyView(Context context, AttributeSet attrs) {
super(context, attrs);
}
#TargetApi(Build.VERSION_CODES.LOLLIPOP)
#Override
protected void onDraw(Canvas canvas) {
super.onDraw(canvas);
Paint paint = new Paint();
paint.setColor(Color.GREEN);
paint.setStrokeWidth(2);
paint.setStyle(Paint.Style.STROKE);
Path path = new Path();
PathMeasure pm = new PathMeasure(path, false);
if (iCurStep <= 20) {
pm.getPosTan(fSegmentLen * iCurStep, afP, null);
path.moveTo(drawPaths[0].x, drawPaths[0].y);
for (int i = 1; i < drawPaths.length; i++) {
path.lineTo(drawPaths[i].y, drawPaths[i].y);
}
canvas.drawPath(path, paint);
iCurStep++;
} else {
iCurStep = 0;
}
}
}
What Im expecting is that, it has to be a growing view...I mean the view has to grow as it is being drawn...But Im not able to produce to do so...How can I be able to sort this out?
Use a ObjectAnimator.
Create your view as normal, but then create an ObjectAnimator (docs: https://developer.android.com/reference/android/animation/ObjectAnimator.html). You'll do something like this in your fragment or activity (you can make it in XML as well). Essentially you create an ObjectAnimator for an object with a property string. The property string must have a camelCase setter. So if you wanted to call view.setScaleX() as the property you were going to modify, you'd need to set the property string to 'scaleX'. Here's a simple example.
View growMe = new View(args);
float startSize = 1.0f;
float endSize = 2.0f;
ObjectAnimator grower = ObjectAnimator.ofFloat(growMe, "scaleX", startSize, endSize);
int durationMs = 1000;
grower.setDuration (durationMs)
grower.start();
The one other thing you'll need to do is add an AnimationListener to your View that actually adjusts the size to be the correct as the end of the animation as (I believe--I might be wrong on this part), the view will re-size after the animation ends.
I hope that helps!
I'm trying to achieve a visual effect, that if I could make would look awesome! The login of the app that I'm doing looks like this:
Keep in mind that the image on the background is an animation, that makes a slightly transition from that image to another.
What I want is make the title of the app "Akrasia" be transparent, but transparent meaning that you can see the image in background through the title letters, this means that in some way I must override the onDraw method of the RelativeLayout that contains this form. I tried to do that, but the only thing that I got was errors. Maybe I'm wrong trying to override the onDraw method in boths, the TextView and the RelativeLayout, maybe there's an easiest way to do it. What do you think? Or maybe is impossible to achive this effect?
UPDATE:
This is how it should look like.
Also I tried to make a custom view extending from TextView wich has a method setBackgroundView wich stores a view instance into a field. Later on the onDraw method and I managed to get the bitmap from the background image. But I don't know how draw it using canvas.
UPDATE:
I make it work! Now I only need change that blue-like background by the drawable of the background.
The view:
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);
init();
}
private void init() {
mPaint = new Paint();
mPaint.setXfermode(new PorterDuffXfermode(PorterDuff.Mode.DST_OUT));
}
public SeeThroughTextView(Context context, AttributeSet attrs) {
super(context, attrs);
init();
}
public SeeThroughTextView(Context context, AttributeSet attrs, int defStyleAttr) {
super(context, attrs, defStyleAttr);
init();
}
#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);
}
}
And in my fragment I have this because the animation in the background:
vBackground.addOnLayoutChangeListener(new View.OnLayoutChangeListener() {
#Override
public void onLayoutChange(View v, int left, int top, int right, int bottom, int oldLeft, int oldTop, int oldRight, int oldBottom) {
vTitle.setBackgroundDrawable(new BitmapDrawable(vBackground.getDrawingCache()));
vTitle.invalidate();
}
});
Nailed!
The view:
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);
init();
}
private void init() {
Typeface myTypeface = Typeface.createFromAsset(getContext().getAssets(), "fonts/gillsans.ttf");
setTypeface(myTypeface);
mPaint = new Paint();
mPaint.setXfermode(new PorterDuffXfermode(PorterDuff.Mode.DST_OUT));
}
public SeeThroughTextView(Context context, AttributeSet attrs) {
super(context, attrs);
init();
}
public SeeThroughTextView(Context context, AttributeSet attrs, int defStyleAttr) {
super(context, attrs, defStyleAttr);
init();
}
#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);
}
}
In my fragment:
#Override
public void onViewCreated(View view, Bundle savedInstanceState) {
super.onViewCreated(view, savedInstanceState);
vLoginBtn = (Button) view.findViewById(R.id.btn_login);
vRegistrationBtn = (Button) view.findViewById(R.id.btn_registration);
vForgotBtn = (Button) view.findViewById(R.id.btn_forgot);
vBackground = (KenBurnsView) view.findViewById(R.id.login_background);
vTitle = (SeeThroughTextView) view.findViewById(R.id.txt_view_login_title);
vBackground.setResourceUrls(
"http://www.youwall.com/papel/peaceful_place_wallpaper_4f3f3.jpg",
"http://www.fwallpaper.net/wallpapers/P/E/Peaceful-Scenary_1920x1200.jpg",
"http://p1.pichost.me/i/39/1620902.jpg"
);
vBackground.addOnLayoutChangeListener(new View.OnLayoutChangeListener() {
#Override
public void onLayoutChange(View v, int left, int top, int right, int bottom, int oldLeft, int oldTop, int oldRight, int oldBottom) {
vTitle.setBackgroundDrawable(getResources().getDrawable(R.drawable.drawable_background_login_top));
vTitle.invalidate();
vBackground.removeOnLayoutChangeListener(this);
}
});
}
The drawables are just two shapes, one with the top-left corner and top-right corner with radius 10dp and the another one with the radius in the bottoms.
The custom TextView with the top drawable shape is alligned above the RelativeLayout wich contains the EditTexts.
No much rocket science. Thanks a lot to #Klotor for suggesting the idea!
Specify a new color in your res/values/colors.xml file (create one if it doesn't exist), the file might look like:
<?xml version="1.0" encoding="utf-8"?>
<resources>
<color name="ltGray">#33999999</color>
</resources>
where the first two numbers are transparency (00 - fully transparent, FF - fully opaque).
Then simply set the text color of desired TextView to #color/ltGray in the xml of that layout, or go
tvTitle.setTextColor(getResources().getColor(R.color.ltGray))
after instatiating the TextView.
Hy, I'm trying to do an animation of a circle drawn on canvas. I can do that pretty easily with ObjectAnimator, however I'd like to start the animation when the view finishes loading or finishes drawing. If I start the animation in init(), the animation property will be "ahead" of the actual drawing, so I need to start it on a callback when the whole view is properly set up. I could do that onMeasure() or onSizeChanged() but those two get called too many times and if i have nested layouts it doesn't work properly. If I use startDelay() it works but I don't think that is an accurate procedure.
Here is a basic custom view class with animation property that changes the radius of a circle.
public class CustomView extends View {
private static final String TAG = CustomView.class.toString();
public CustomView(final Context context) {
super(context);
init(context);
}
public CustomView(final Context context, final AttributeSet attrs) {
super(context, attrs);
init(context);
}
public CustomView(final Context context, final AttributeSet attrs, final int defStyle) {
super(context, attrs, defStyle);
init(context);
}
private void init(final Context context) {
// OUTER CIRCLE PAINT
mPaint = new Paint();
// Adds anti-aliasing to drawed elements
mPaint.setAntiAlias(true);
mPaint.setFilterBitmap(true);
mPaint.setStrokeWidth(1);
mPaint.setStrokeCap(Paint.Cap.SQUARE);
mPaint.setStyle(Paint.Style.FILL);
final int animationTime = getResources().getInteger(ANIMATION_TIME_ID);
progressAnimator = ObjectAnimator.ofFloat(this, "animProgress", 0f, 0f);
progressAnimator.setDuration(animationTime);
Log.d(TAG, "Init ended");
//startAnimationCircle(50f);
}
#Override
public void onDraw(final Canvas canvas) {
super.onDraw(canvas);
canvas.drawCircle(this.getWidth()/2, this.getHeight()/2, animProgress, mPaint);
}
#Override
protected void onSizeChanged (int w, int h, int oldw, int oldh) {
//startAnimationCircle(50f);
}
/**
* onMeasure() is called automatically right after a call to measure()
*/
#Override
protected void onMeasure(final int widthMeasureSpec, final int heightMeasureSpec) {
super.onMeasure(widthMeasureSpec, heightMeasureSpec);
//startAnimationCircle(50f);
}
private Paint mPaint;
private static final int ANIMATION_TIME_ID = android.R.integer.config_mediumAnimTime;
private float animProgress;
private ObjectAnimator progressAnimator;
public float getAnimProgress() {
return animProgress;
}
public void setAnimProgress(float animProgress) {
this.animProgress = animProgress;
this.invalidate();
}
public void startAnimationCircle(float size) {
progressAnimator.setFloatValues(animProgress, size);
//progressAnimator.setStartDelay(2000);
progressAnimator.start();
}
}
And the XML also.
<com.your-package.CustomView
android:layout_width="match_parent"
android:layout_height="match_parent" />
I have this class that extends View:
public class DrawingPanel extends View {
private Bitmap toDisk;
private int w;
private int h;
public DrawingPanel(Context context, AttributeSet attrs) {
super(context, attrs);
System.out.println("Drawing");
p0 = new Paint();
p0.setStyle(Style.STROKE);
}
#Override
public boolean onTouchEvent(MotionEvent event) {
...
}
#Override
protected void onSizeChanged(int w, int h, int oldw, int oldh) {
this.w = w;
this.h = h;
toDisk = Bitmap.createBitmap(w, h, Bitmap.Config.ARGB_8888);
super.onSizeChanged(w, h, oldw, oldh);
}
#Override
public void onDraw(Canvas canvas) {
canvas.drawColor(Color.WHITE);
canvas.drawPath(path0, p0);
canvas.setBitmap(toDisk);
}
}
I use this class as a child view in a layout. In the Graphical view of Eclipse all things are OK, and all views are visible, but in emulator my class (DrawingPanel) hides other views that are below it. When I remove this line:
canvas.setBitmap(toDisk);
all views will be visible again. And another strange thing: when I put that line in the first line of onDraw method like this:
#Override
public void onDraw(Canvas canvas) {
canvas.setBitmap(toDisk);
canvas.drawColor(Color.WHITE);
canvas.drawPath(path0, p0);
}
my DrawingPanel and other views that are below it will be invisible. How can I solve this problem?
My purpose is to save what user draws on canvas.
You misunderstand setBitmap. It sets a bitmap for you to send new draw commands to INSTEAD OF the screen. It does not draw the previous commands to a bitmap. To do that, you'd want to do this:
Canvas diskCanvas = new Canvas(toDisk);
//Send all draw commands to this canvas
screenCanvas.drawBitmap(toDisk, new Matrix(), new Paint());
Note that this will only save the bitmap to memory. To save to disk, you need to save it to a file. Do NOT do that in onDraw, its a slow operation.