ANDROID: Custom view implemtation - android

I have a custom view. When will android call draw() on it? After it calls onMeasure and onLayout?
'package global.domination.pack;
import android.content.Context;
import android.graphics.Bitmap;
import android.graphics.Canvas;
import android.util.Log;
import android.view.View;
public class TileView extends View {
// rules about what can be and is on this tile
boolean bLand;
boolean bSea;
boolean bAir;
boolean bOccupied;
Character _character;
// this tile's coordinates
int nX;
int nY;
// bitmaps used for drawing this tile
Bitmap _originalBitmap;
Bitmap _finalBitmap;
// this constructor initializes all variables (creates the tile)
public TileView(Context context, boolean isLand, boolean isSea, boolean isAir, boolean isOccupied, int x_Coordinate, int y_Coordinate, Bitmap bitmap) {
super(context);
//Initialize variables and set tile graphic.
bLand = isLand;
bSea = isSea;
bAir = isAir;
bOccupied = isOccupied;
nX = x_Coordinate;
nY = y_Coordinate;
_originalBitmap = bitmap;
_character = null;
_finalBitmap = bitmap;
}
/* This method is called by parent view when it
* wants to know this view's drawing preferences
*/
#Override
protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
// set this tiles dimensions
this.setMeasuredDimension(50, 50);
}
/* This method is called by parent view when it wants
* this view to draw itself.
*/
#Override
protected void onDraw(Canvas canvas) {
// attempt to draw the _finalBitmap (tile graphic with character graphic)
try{
canvas.drawBitmap(_finalBitmap, 0, 0, null);
}
catch(RuntimeException rtException){
Log.e("EXCEPTION", "ERROR DRAWING FINAL BITMAP" + rtException.getMessage() , rtException);
}
}
}
'

Are you overriding the onMeasure() of the ViewGroups? If so you need to remember to call measure() on you child views.

onMeasure() is always called whenever a view have to measure dimensions of its own and child views.
If you are going to implement the viewgroup then its child view's onDraw() method will be called whenever a viewgroup has to dispatch draw event to all its childs after onLayout().
Here is a image link which may help you understanding view lifecycle flow:
For brief description you may read the official doc.

Related

How to use a SurfaceView from an Adapter (Android)

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?

Drawing over a view and all it's children

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
}

Android: Are there any good resources for building Java Only views WITHOUT XML

I've been going through all of the documentation, and examples found on the web, but I have yet to find any good resources on building custom views using Java ONLY. I have seen many that describe about creating them using XML and Java.
I think the thing that I'm struggling with the most are relatively simple things like, "can I use a "TextView" widget in a custom view AND also use the canvas to draw some graphics? I've been successful doing one or the other but can't seem to find ways to do both simultaneously.
For example: I would like to have an TextView widget at the top of a view with a touchable graphic underneath that acts as a dial and is drawn onto the canvas which takes up all available space once the TextView is added. As I turn the dial, it increments text inside the TextView. I would think something like the following would work... but it doesn't and it most likely because I'm missing something simple.
public class Dial extends LinearLayout implements View.OnTouchListener
{
Bitmap _dial;
protected int bpm = 120;
TextView txtBpm;
/**
* Constructor
* #param context
*/
public Dial(Context context)
{
super(context);
_dial = BitmapFactory.decodeResource(getResources(), R.drawable.dial_small);
txtBpm = new TextView(context);
txtBpm.setTextSize(43);
txtBpm.setWidth(100);
txtBpm.setText(String.valueOf(bpm));
this.addView(txtBpm);
}
#Override
protected void onDraw(Canvas canvas)
{
super.onDraw(canvas);
// Setting the y-pos to 160 so even
// if the canvas fills the view,
// the dial will still be below the textview.
canvas.drawBitmap(_dial, 0,160,null);
}
#Override
public void onSizeChanged(int w, int h, int oldW, int oldH)
{
}
/* (non-Javadoc)
* #see android.view.View.OnTouchListener#onTouch(android.view.View, android.view.MotionEvent)
*/
#Override
public boolean onTouch(View arg0, MotionEvent arg1)
{
// Do some stuff here to rotate the dial
return false;
}
}

Android Drawing

I'm quite new to android, but basically I want to set up a program so that when the user clicks on an imageview, a dot is drawn where they click. I have tried many times but just don't seem to be able to get it to work, and help would be much appreciated.So far I have
package com.smallbore.smallbore;
import android.app.Activity;
import android.graphics.drawable.ShapeDrawable;
import android.graphics.drawable.shapes.OvalShape;
import android.os.Bundle;
import android.view.MotionEvent;
import android.view.View;
import android.widget.ImageView;
import android.widget.TextView;
public class targetenter extends Activity {
/** Called when the activity is first created. */
#Override
public void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.targetenter);
ImageView v = (ImageView) findViewById(R.id.imageView1);
v.setOnTouchListener(new View.OnTouchListener() {
#Override
public boolean onTouch(View arg0, MotionEvent arg1) {
TextView t1 = (TextView) findViewById(R.id.textView2);
TextView t2 = (TextView) findViewById(R.id.textView3);
t1.setText("X: "+arg1.getX());
t2.setText("Y: "+arg1.getY());
int x = (int)arg1.getX();
int y = (int)arg1.getY();
int width = 50;
int height = 50;
ShapeDrawable mDrawable = new ShapeDrawable(new OvalShape());
mDrawable.getPaint().setColor(0xff74AC23);
mDrawable.setBounds(x, y, x + width, y + height);
ImageView v = (ImageView) findViewById(R.id.imageView1)
v.setImageDrawable(mDrawable);
return false;
}
});
};
}
To draw the dots, you'll probably have to use a custom View and override onTouchEvent and onDraw. onTouchEvent will give you the x,y coordinates of the touch event and in onDraw you can draw a circle at this point to the canvas that the framework provides to this method. If you want to clear previous dots, you only need to keep track of the last x,y coordinates. Otherwise, you'll need to keep a running list (ArrayList or something like that).
In your shoes, I'd probably subclass ImageView so that I get the image drawing stuff for free. Call super.onDraw(canvas) inside the overridden onDraw method, and then draw your dots (canvas.drawCircle).
The Android SDK contains a pretty good example : http://developer.android.com/resources/samples/ApiDemos/src/com/example/android/apis/graphics/DrawPoints.html
You just need to make SampleView handling click event, and get it displaying the image in SampleView.onDraw method.

Using Layout resources in a LiveWallpaper on Android

When you create a LiveWallpaper in Android 2.2+ you get a canvas (or whatever the 3D equivalent is) to draw on. I'd like to draw some elements using the built-in Android UI tools rather than building everything from scratch using canvas commands or a loading a pre-rendered UI bitmap.
Converting a single View to a Bitmap works fine. i.e. this works great:
// For example this works:
TextView view = new TextView(ctx);
view.layout(0, 0, 200, 100);
view.setText("test");
Bitmap b = Bitmap.createBitmap( 200, 100, Bitmap.Config.ARGB_8888);
Canvas tempC = new Canvas(b);
view.draw(tempC);
c.drawBitmap(b, 200, 100, mPaint);
But, converting a LinearLayout with children causes problems. You only get the LinearLayout itself and none of it's children. For example, if I set the LinearLayout to have a white background I get a nicely rendered white box, but none of the TextView children are in the Bitmap. I've also tried using DrawingCache with similar results.
The code I'm using is the cube example with the only changes being an extra draw command. The LinearLayout works fine as a toast or as a regular view (i.e. everything nicely shows up), on the LiveWallpaper all I get is the LinearLayout's background rendered.
inflater = (LayoutInflater)getApplicationContext().getSystemService(Context.LAYOUT_INFLATER_SERVICE);
layout = (LinearLayout) inflater.inflate(com.example.android.livecubes.R.layout.testLinearLayout, null);
layout.layout(0, 0, 400, 200);
Bitmap b = Bitmap.createBitmap( 400, 200, Bitmap.Config.ARGB_8888);
Canvas tempC = new Canvas(b);
layout.draw(tempC);
c.drawBitmap(b, 10, 200, mPaint);
Does anyone know if you need to do anything special to get the children rendered properly to my bitmap? i.e. do I need to somehow do something special to make the layout render the rest of the children? Should I write a function to recursively do something to all the children?
I could composite everything myself but, since the display is fairly static (i.e. I draw this once and keep a copy of the bitmap to draw on the background) this seems easier on me and still pretty efficient.
Edit:
While digging a bit more into the state of the Layout it looks as though the layout is not progressing down the view tree (i.e. the LinearLayout gets its layout computed when I call layout() but the children have a null (0x0) size). According to the Romain Guy's post in 2008 android developer post. You have to wait for the layout pass or force the layout yourself. The problem is how can I wait for a layout pass from a wall paper engine for a LinearLayout that is not attached to the root view group? And how can I manually layout each child element if layout requires you to set the left, top, right, bottom when I don't know what these should be.
I've tried calling forceLayout on the children but it doesn't seem to help either. I'm not sure how the layout framework works behind the scenes (besides that it does a two pass layout). Is there a way to manually make it do the layout pass, i.e. right now? Since it's not an Activity I don't think a lot of the normal background stuff is happening quite the way I'd like.
Live Wallpapers were very specifically designed to NOT use standard UI widgets. However it is possible to use them anyway. You will have to force a layout pass yourself by first calling measure() on the View, then layout(). You can find more information in my presentation.
Here's an example of a view group, button and imageview laid out and displayed in a Live Wallpapers. You can also work around the null window token bug and add views directly via WindowManager if you set the the Window type to 0. You have to catch the exception it throws and the results are somewhat erratic but it works for the most part.
import android.content.Context;
import android.graphics.Canvas;
import android.graphics.PixelFormat;
import android.service.wallpaper.WallpaperService;
import android.util.Log;
import android.view.Gravity;
import android.view.SurfaceHolder;
import android.view.SurfaceHolder.Callback;
import android.view.ViewGroup;
import android.view.WindowManager;
import android.view.WindowManager.LayoutParams;
import android.widget.Button;
import android.widget.ImageView;
import android.widget.LinearLayout;
public class UIWidgetWallpaper extends WallpaperService
{
private final String TAG = getClass().getSimpleName();
final static int pixFormat = PixelFormat.RGBA_8888;
protected ImageView imageView;
protected WindowManager windowManager;
protected LayoutParams layoutParams;
protected WidgetGroup widgetGroup;
protected SurfaceHolder surfaceHolder;
protected Button button;
#Override
public Engine onCreateEngine()
{
Log.i( TAG, "onCreateEngine" );
return new UIWidgetWallpaperEngine();
}
public class WidgetGroup extends ViewGroup
{
private final String TAG = getClass().getSimpleName();
public WidgetGroup( Context context )
{
super( context );
Log.i( TAG, "WidgetGroup" );
setWillNotDraw( true );
}
#Override
protected void onLayout( boolean changed, int l, int t, int r, int b )
{
layout( l, t, r, b );
}
}
public class UIWidgetWallpaperEngine extends Engine implements Callback
{
private final String TAG = getClass().getSimpleName();
#Override
public void onCreate( SurfaceHolder holder )
{
Log.i( TAG, "onCreate" );
super.onCreate( holder );
surfaceHolder = holder;
surfaceHolder.addCallback( this );
imageView = new ImageView( getApplicationContext() );
imageView.setClickable( false );
imageView.setImageResource( R.drawable.icon );
widgetGroup = new WidgetGroup( getApplicationContext() );
widgetGroup.setBackgroundDrawable( getWallpaper() );
widgetGroup.setLayoutParams( new LinearLayout.LayoutParams( LayoutParams.MATCH_PARENT, LayoutParams.MATCH_PARENT ) );
widgetGroup.setAddStatesFromChildren( true );
holder.setFormat( pixFormat );
LinearLayout.LayoutParams imageParams = new LinearLayout.LayoutParams( LayoutParams.MATCH_PARENT, LayoutParams.MATCH_PARENT );
imageParams.weight = 1.0f;
imageParams.gravity = Gravity.CENTER;
widgetGroup.addView( imageView, imageParams );
button = new Button( getApplicationContext() );
button.setText( "Test Button" );
LinearLayout.LayoutParams buttonParams = new LinearLayout.LayoutParams( LayoutParams.MATCH_PARENT, LayoutParams.MATCH_PARENT );
buttonParams.weight = 1.0f;
buttonParams.gravity = Gravity.CENTER_HORIZONTAL | Gravity.BOTTOM;
widgetGroup.addView( button, buttonParams );
}
#Override
public void surfaceChanged( SurfaceHolder holder, int format, int width, int height )
{
Log.i( TAG, "surfaceChanged: " );
synchronized( surfaceHolder )
{
Canvas canvas = surfaceHolder.lockCanvas();
widgetGroup.layout( 0, 0, width, height );
imageView.layout( 0, 0, width / 2, height );
button.layout( width / 2, height - 100, width, height );
widgetGroup.draw( canvas );
surfaceHolder.unlockCanvasAndPost( canvas );
}
}
#Override
public void surfaceCreated( SurfaceHolder holder )
{
}
#Override
public void surfaceDestroyed( SurfaceHolder holder )
{
}
}
}

Categories

Resources