I'm trying to apply a visual effect to a viewgroup. My idea is to grab a bitmap of the viewgroup, shrink it down, expand it back up, and draw it over the viewgroup to give it a blocky, low quality effect.
I've got most of the way there using this code:
public class Blocker {
private static final float RESAMPLE_QUALITY = 0.66f; // less than 1, lower = worse quality
public static void block(Canvas canvas, Bitmap bitmap_old) {
block(canvas, bitmap_old, RESAMPLE_QUALITY);
}
public static void block(Canvas canvas, Bitmap bitmap_old, float quality) {
Bitmap bitmap_new = Bitmap.createScaledBitmap(bitmap_old, Math.round(bitmap_old.getWidth() * RESAMPLE_QUALITY), Math.round(bitmap_old.getHeight() * RESAMPLE_QUALITY), true);
Rect from = new Rect(0, 0, bitmap_new.getWidth(), bitmap_new.getHeight());
RectF to = new RectF(0, 0, bitmap_old.getWidth(), bitmap_old.getHeight());
canvas.drawBitmap(bitmap_new, from, to, null);
}
}
I simply pass in the canvas to draw on and a bitmap of what needs to be scaled down+up and it works well.
public class BlockedLinearLayout extends LinearLayout {
private static final String TAG = BlockedLinearLayout.class.getSimpleName();
public BlockedLinearLayout(Context context, AttributeSet attrs) {
super(context, attrs);
applyCustomAttributes(context, attrs);
setup();
}
public BlockedLinearLayout(Context context) {
super(context);
setup();
}
private void setup() {
this.setDrawingCacheEnabled(true);
}
#Override
public void draw(Canvas canvas) {
super.draw(canvas);
// block(canvas); If I call this here, it works but no updates
}
#Override
public void onDraw(Canvas canvas) {
// block(canvas); If I call this here, draws behind children, still no updates
}
private void block(Canvas canvas) {
Blocker.block(canvas, this.getDrawingCache());
}
}
The problem I'm having is in my viewgroup. If I run the block method in the viewgroup's draw, it draws over everything but doesn't ever update when child views change. I've traced function calls with Log, and the draw method seems to be running, but nothing changes.
I've also tried implementing this in onDraw. This draws the bitmap behind all the children views, and again they aren't updating.
Can anyone explain how I would go about fixing this?
Try this:
#Override
protected void dispatchDraw(Canvas canvas) {
// call block() here if you want to draw behind children
super.dispatchDraw(canvas);
// call block() here if you want to draw over children
}
And call destroyDrawingCache() and then, buildDrawingCache() each time you change a child.
Draw() method will work well for you.
I'm now trying to make a count time view in a circle shape, when time is passing, the view will reducing his angle. It's used to cover profile photo(a circle shape photo).
Starting with Android API 23, you can use onDrawForeground(Canvas) to draw on top of child views: https://developer.android.com/reference/android/view/View#onDrawForeground(android.graphics.Canvas)
Unlike onDraw() though, be sure to call through to the super class:
#Override
public void onDrawForeground(final Canvas canvas) {
super.onDrawForeground(canvas);
// Your code here
}
Related
I'm doing a school project. In this project I have to do a program that have one or more ball bouncing in the screen. I did some research on google to help me in this, and I found this code :
public class BouncingBallInside extends View {
private List<Ball> balls = new ArrayList<>();
public BouncingBallInside(Context context, AttributeSet attrs) {
super(context, attrs);
init();
}
public BouncingBallInside(Context context) {
super(context);
init();
}
private void init(){
//Add a new ball to the view
balls.add(new Ball(50,50,100, Color.RED));
}
#Override
protected void onDraw(Canvas canvas) {
super.onDraw(canvas);
//Draw the balls
for(Ball ball : balls){
//Move first
ball.move(canvas);
//Draw them
canvas.drawOval(ball.oval,ball.paint);
}
invalidate(); // See note
}
}
The ball class :
public class Ball{
public int x,y,size;
public int velX = 10;
public int velY=7;
public Paint paint;
public RectF oval;
public Ball(int x, int y, int size, int color){
this.x = x;
this.y = y;
this.size = size;
this.paint = new Paint();
this.paint.setColor(color);
}
public void move(Canvas canvas) {
this.x += velX;
this.y += velY;
this.oval = new RectF(x-size/2,y-size/2,x+size/2,y+size/2);
//Do we need to bounce next time?
Rect bounds = new Rect();
this.oval.roundOut(bounds); ///store our int bounds
//This is what you're looking for ▼
if(!canvas.getClipBounds().contains(bounds)){
if(this.x-size<0 || this.x+size > canvas.getWidth()){
velX=-velX;
}
if(this.y-size<0 || this.y+size > canvas.getHeight()){
velY=-velY;
}
}
}
}
The program works perfecly.
I studied it deeply as good as I could. But after it and after watching the documentation I couldn't understand two thing:
Where and when the method onDraw(Canvas canvas) is called the first time.
Why at the end of onDraw there is invalidate()?
I mean the documentation said :
Invalidate the whole view. If the view is visible, onDraw(android.graphics.Canvas) will be called at some point in the future.
so... if this method is used to call onDraw,why don't call it direcly? what's the difference?
1)The onDraw method will be called by the framework, whenever the view is invalid. A view is invalid when it first comes on screen, so when you set your content view for an activity they layout and all views in it will be measured, laid out, then drawn (via onDraw).
After that the UI thread will call onDraw if needed every 16ms or so (so it draws at 60 FPS).
2)Its marking the view as needing to be redrawn, so the next time the the screen is drawn onDraw will be called. Otherwise it would be skipped, as we could assume it isn't needed.
Why you don't call onDraw directly- efficiency. In a very simple drawing system you would- but drawing is time consuming, you don't want to do it more than you have to. So instead of drawing immediately (which wouldn't work anyway, you wouldn't have the right Canvas to pass to onDraw), you call invalidate and the system will call onDraw if needed at a regular interval.
Note that this isn't particularly good code. In particular, having the onDraw trigger the move which updates the balls location instead of using a timer is icky. Having onDraw call invalidate as a result is also kind of icky. A better solution would be to separate the view, model, and timer into more of an MVC or MVP system.
I've just started playing around with developing an Android app and have run into some troubles.
The intention of this testing app is to draw a square to the screen that moves towards the bottom right of the screen. Simple as that.
MainActivity class (current entry point) looks like so:
Main Activity class (current entry point) looks like so:
public class MainActivity extends Activity {
Canvas canvas;
GameLoopThread gameThread;
#Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState); //Super constructor
gameThread=new GameLoopThread(); //Create GameLoop instance
//Create mySurfaceView instance and pass it the new gameloop
MySurfaceView sView=new MySurfaceView(this,gameThread);
//Without this only the bit I cant remove is drawn
sView.setBackgroundColor(Color.TRANSPARENT);
setContentView(sView); //Set the current ContentView to the one we created
//Pass the GameThread the MySurfaceView to repeatedly update
gameThread.setSurfaceView(sView);
gameThread.start(); //Start the thread
}
}
GameLoopThread looks like so:
public class GameLoopThread extends Thread {
protected volatile boolean running;
private MySurfaceView view;
public GameLoopThread(){
}
public void setSurfaceView(MySurfaceView view){
this.view=view;
}
#Override
public void run() {
running=true;
while(running){
Canvas c = null;
c = view.getHolder().lockCanvas(); //Get the canvas
if (c!=null) {
synchronized (view) {
view.draw(c); //Run the doDraw method in our MySurfaceView
}
}
try {
sleep(30, 0); //Throttle
}
catch(InterruptedException e){
e.printStackTrace();
}
if (c != null) {
view.getHolder().unlockCanvasAndPost(c); //Lock and post canvas
}
}
}
public void terminate(){
running=false;
}
}
And finally MySurfaceView looks like so:
public class MySurfaceView extends SurfaceView {
private Bitmap bmp;
private SurfaceHolder holder;
private final GameLoopThread gameLoop;
Paint paint;
float x=0;
float y=0;
public MySurfaceView(Context c, GameLoopThread gThread){
super(c);
holder=getHolder();
gameLoop=gThread;
paint=new Paint();
paint.setColor(Color.CYAN);
paint.setStrokeWidth(10);
holder.addCallback(new CallBack());
}
#Override
protected void onDraw(Canvas canvas) {
canvas.drawColor(0, PorterDuff.Mode.CLEAR);
paint.setColor(paint.getColor() - 1);
canvas.drawRect(x, y, x + 50, y + 50, paint);
x++;
y++;
}
private class CallBack implements SurfaceHolder.Callback{
#Override
public void surfaceDestroyed(SurfaceHolder holder) {
gameLoop.terminate();
while (true) {
try {
gameLoop.join();
break;
} catch (InterruptedException e) {
}
}
}
#Override
public void surfaceCreated(SurfaceHolder holder) {
}
#Override
public void surfaceChanged(SurfaceHolder holder, int format,
int width, int height) {
}
}
}
The Issue
This all works, except that one of the initial few draws to the view "sticks". It remains over top of anything drawn at a later date. See below:
I cannot fathom why this is happening. No amount of clearing fixes the problem. If I stop drawing the 'new' square the 'stuck' square remains. You can see I'm varying the color of the 'new' square to test if it changes the 'stuck' one which would indicate it was being redraw. Clearly it isn't.
Drawing nothing for around 4 loops with a 30ms pause in between each draw results in no 'stuck' square. Starting drawing after those initial 4 results in a square that moves across the screen as it should.
Varying the pause time changes how many loops must be waited, but the relationship doesn't appear to be proportional.
Other Info
This is being run on a Samsung Galaxy SIII Mini
SDK verson 4.0.3
A SurfaceView has two parts, a Surface and a View. When you get a Canvas with lockCanvas(), you're getting a Canvas for the Surface part. The Surface is a separate layer, independent of the layer used for all of the View elements.
You've subclassed SurfaceView and provided an onDraw() function, which the View hierarchy uses to render onto the View portion. My guess is that the View hierachy gets an invalidate and decides to draw on the View part of the SurfaceView. Your experiment of skipping the rendering for the first few loop iterations works because you're skipping the render that happens on the invalidate. Because the Surface layer is drawn behind the View layer, you see the onDraw()-rendered square on top of the other stuff you're rendering.
Normally you don't draw on the View; it's just a transparent place-holder, used by the layout code to leave a "hole" in the View layer where the Surface will show through.
Rename onDraw() to doDraw(), and drop the #override. I don't think there's a reason to subclass SurfaceView at all. That should prevent the SurfaceView from drawing on its View.
Of course, if you want to have a mask layer, perhaps to add rounded corners or a "dirt" effect, drawing on the View is an easy way to accomplish it.
See the Graphics Architecture doc for the full story.
I have a GridView. I would like each item to be an object derived form SurfaceView. I have created a class which extends SurfaceView, and an Adapter whose getView() returns an instance of this.
All I get is a little black square for each grid item. The SurfaceView's draw() is never called (shown by adding trace) and neither is its surfaceCreated().
How do I do this in a way which allows me to draw stuff on the SurfaceView please? I think the crux of the problem is that surfaceCreated() is never called. Why not?
This is my Adapter:
public class LevelAdapter extends BaseAdapter
{
...
#Override
public View getView(int position, View convertView, ViewGroup parent)
{
LevelNumberView number = new LevelNumberView(m_context);
number.setLayoutParams(new GridView.LayoutParams(70, 70));
number.setPadding(1, 1, 1, 1);
number.masDraw(); // Experiment - but it doesn't work
return number;
}
}
Then my SurfaceView class:
public class LevelNumberView extends SurfaceView
implements SurfaceHolder.Callback
{
...
#Override
public void surfaceCreated(SurfaceHolder holder)
{
m_surfaceHolder = holder;
}
#Override
public void draw(Canvas canvas)
{
Log.i("LevelNumberView","Draw");
doDraw(canvas, Color.YELLOW);
}
public void masDraw()
{
Log.i("LevelNumberView","masDraw");
if (null != m_surfaceHolder)
{
Canvas canvas = m_surfaceHolder.lockCanvas();
if (null != canvas)
{
doDraw(canvas, Color.GREEN);
m_surfaceHolder.unlockCanvasAndPost(canvas);
}
}
}
private void doDraw(Canvas canvas, int colour)
{
int w = canvas.getWidth();
int h = canvas.getHeight();
int min = Math.min(w,h);
Paint paint = new Paint(Paint.ANTI_ALIAS_FLAG);
paint.setColor(colour);
paint.setStyle(Paint.Style.FILL);
canvas.drawCircle(w/2, h/2, min, paint);
}
}
Thanks!
-Mark
Overriding draw() probably isn't what you want. If you want to draw on the View, override onDraw(). If you want to draw on the Surface, that's usually done from a separate thread. (The SurfaceView has two parts; the surface part is a completely separate layer that is drawn behind the layer with the View-based UI.)
If you haven't called SurfaceHolder().addCallback(), no object will be receiving callbacks -- the SurfaceView doesn't send callbacks to itself, so subclassing SurfaceView doesn't really do much for you.
Perhaps what you want is simply a custom View?
My project is about image processing in android.I have a bitmap that I have loaded from a resource file (an PNG image). I want to draw it. But I couldn't.
Here my code snippet:
mB = BitmapFactory.decodeResource(getResources(), R.drawable.picture);
Canvas c = new Canvas(mB);
Paint p = new Paint();
c.drawBitmap(mB,0,0,p);
it didn't work. Is the code true? .Is there any thing more that I must do?
You should use an ImageView instead and load it by
imageView.setImageResource(R.drawable.picture);
If you want to manually draw it with a Canvas, you have to use a canvas that is passed into a draw() method and implement a Custom View.
Update to add example CustomView:
public class CustomView extends View {
private Paint mPaint;
private Drawable mDrawable;
public CustomView(Context context, AttributeSet attrs) {
super(context, attrs);
mPaint = new Paint();
mDrawable = context.getResources().getDrawable(R.drawable.some_drawable);
}
#Override
protected void onDraw(Canvas canvas) {
super.onDraw(canvas);
mDrawable.draw(canvas);
}
}
There are a couple of things you are missing.
First, I think you're misunderstanding the Canvas(Bitmap b) constructor. The Bitmap passed in there is one that the Canvas will draw into. This could be just a new Bitmap that you constructed.
Second, it is recommended that you use the Canvas that is passed to you in your View's onDraw method. Presumably that View is one from your Activity, either fetched from your XML layout via findViewById or constructed and passed to setContentView in the Activity's onCreate() method.
So, you are going to have to subclass View and override the onDraw method to get your drawing done. Something like:
public class MyView extends View {
#Override
public void onDraw (Canvas c) {
Bitmap mB = BitmapFactory.decodeResource(this.getContext().getResources(), R.drawable.picture);
c.drawBitmap(mB, 0, 0, null);
}
}
Then, in your Activity, you'll need to create an instance of your new View and pass it to the Activity via setContentView:
public class MyActivity extends Activity {
#Override
public void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
mv = new MyView(this);
setContentView(mv);
}
You can instead call the setContentView(View v, ViewGroup.LayoutParameters lp) overload if you want to set up the LayoutParameters.
I haven't tested any of this, but it should at least get you on the right path.
I'm creating a custom widget by extending LinearLayout:
public class MyWidget extends LinearLayout {
private static Paint PAINT = new Paint(Paint.ANTI_ALIAS_FLAG);
static {
PAINT.setColor(Color.RED);
}
public MyWidget(Context context) {
this(context, null);
}
public MyWidget(Context context, AttributeSet attrs) {
super(context, attrs);
}
#Override
protected void onDraw(Canvas canvas) {
super.onDraw(canvas);
canvas.drawCircle(canvas.getWidth() / 2, canvas.getHeight()/2, canvas.getWidth()/2, PAINT);
// never gets called :-(
}
#Override
protected void dispatchDraw(Canvas canvas) {
super.dispatchDraw(canvas);
// this gets called, but with a canvas sized after the padding.
}
}
I can add children just fine, but I'm never getting my custom onDraw() being called. dispatchDraw() gets called, but that seems to have a different canvas (the one that's within the padding. I need to draw on the whole layout area). Is there some flag that needs to get set to get onDraw() called for the layout?
You need to call setWillNotDraw(false) in your constructor.
Because by default a layout does not need to draw, so an optimization is to not call is draw method. By calling setWillNotDraw(false) you tell the UI toolkit that you want to draw.