I'm trying to draw some shapes onto a SurfaceView from within a thread, but nothing is being rendered to the screen. I've looked at similar questions by people having the same problem, but none of the answers have led me to a solution, suggesting a different cause in my particular case.
I've created a simplified version of my code to demonstrate the problem. Rendering is handled by the RenderingTestView class, which implements a custom view derived from SurfaceView. The rendering thread is implemented as a Runnable inside RenderingTestView:
package com.example.renderingtest.app;
import android.content.Context;
import android.graphics.*;
import android.os.Build;
import android.util.AttributeSet;
import android.view.SurfaceHolder;
import android.view.SurfaceView;
public class RenderingTestView extends SurfaceView {
private SurfaceHolder holder;
private Paint paint;
private boolean surfaceCreated = false;
private Thread videoThread;
public RenderingTestView(Context context) {
super(context);
init_view(context);
}
public RenderingTestView(Context context, AttributeSet attrs) {
super(context, attrs);
init_view(context);
}
public RenderingTestView(Context context, AttributeSet attrs, int defStyle) {
super(context, attrs, defStyle);
init_view(context);
}
private void init_view(Context context)
{
if (Build.VERSION.SDK_INT >= 11)
setLayerType(android.view.View.LAYER_TYPE_SOFTWARE, null);
paint = new Paint();
paint.setColor(Color.RED);
holder = getHolder();
holder.addCallback(new SurfaceHolder.Callback() {
#Override
public void surfaceCreated(SurfaceHolder holder) {
surfaceCreated = true;
videoThread = new Thread(videoRunnable);
videoThread.start();
}
#Override
public void surfaceChanged(SurfaceHolder holder, int format, int width, int height) {
// do nothing
}
#Override
public void surfaceDestroyed(SurfaceHolder holder) {
surfaceCreated = false;
}
});
}
private Runnable videoRunnable = new Runnable() {
#Override
public void run() {
Thread.currentThread().setPriority(Thread.NORM_PRIORITY);
while (true) {
if (!surfaceCreated || !holder.getSurface().isValid())
continue;
Canvas c = holder.lockCanvas(null);
try {
synchronized (holder) {
if (c != null)
Draw(c);
}
}
finally {
if (c != null)
holder.unlockCanvasAndPost(c);
}
}
}
};
protected void Draw(Canvas canvas)
{
canvas.drawCircle(0, 0, 100, paint);
}
}
Placing a breakpoint inside Draw() confirms that it's being called successfully.
The layout file looks like this:
<RelativeLayout xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:tools="http://schemas.android.com/tools"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:paddingLeft="#dimen/activity_horizontal_margin"
android:paddingRight="#dimen/activity_horizontal_margin"
android:paddingTop="#dimen/activity_vertical_margin"
android:paddingBottom="#dimen/activity_vertical_margin"
tools:context="com.example.renderingtest.app.RenderingTest" android:background="#000000">
<com.example.renderingtest.app.RenderingTest
android:id="#+id/renderingTestView"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:layout_alignParentRight="true"
android:layout_alignParentEnd="true" android:layout_alignParentTop="true"/>
</RelativeLayout>
Overriding onDraw() in RenderingTestView, like this:
#Override
public void onDraw(Canvas canvas)
{
super.onDraw(canvas);
Draw(canvas);
}
... and calling setWillNotDraw(false) inside init_view() does in fact produce the desired output, but I want to render from inside the Runnable rather than wait for invalidate() to produce a call to onDraw().
After further testing, I've discovered the problem is caused by the following code:
if (Build.VERSION.SDK_INT >= 11)
setLayerType(android.view.View.LAYER_TYPE_SOFTWARE, null);
It turns out calling setLayerType() as above somehow prevents SurfaceView from rendering anything onto the Canvas, and is in any case unnecessary since SurfaceView rendering is always performed in software. When first testing my draw calls I was using a regular View rather than a SurfaceView, and the offending lines were carried over from that.
Related
I am making a project. It draws concentric circles in android canvas. When the user drags the screen, all the circles move accordingly. Here is my code so far.
activity_main.xml:
<?xml version="1.0" encoding="utf-8"?>
<RelativeLayout xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:app="http://schemas.android.com/apk/res-auto"
xmlns:tools="http://schemas.android.com/tools"
android:layout_width="match_parent"
android:layout_height="match_parent"
tools:context=".MainActivity"
android:background="#android:color/white">
<RelativeLayout
android:id="#+id/scrollableSpace"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:layout_alignParentRight="true"
android:layout_alignParentBottom="true"
android:layout_alignParentTop="true"
android:layout_alignParentLeft="true">
<project.myProject.DrawOrbit
android:id="#+id/orbitsRegion"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:layout_alignParentRight="true"
android:layout_alignParentBottom="true"
android:layout_alignParentTop="true"
android:layout_alignParentLeft="true" />
</RelativeLayout>
</RelativeLayout>
MainActivity.java
public class MainActivity extends AppCompatActivity implements View.OnTouchListener
{
PointF center;
center.x=500;center.y=500;
float radius[]={100,200,300,400,500};
DrawOrbit orbit;
int startX=0,startY=0,currentX=0,currentY=0;
#Override
protected void onCreate(Bundle savedInstanceState)
{
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
RelativeLayout layout=(RelativeLayout)findViewById(R.id.scrollableSpace);
orbit.draw(center,radius);
layout.setOnTouchListener(this);
}
#Override
public boolean onTouch(View v, MotionEvent motionEvent)
{
final int action= motionEvent.getAction();
switch(action & MotionEvent.ACTION_MASK)
{
case MotionEvent.ACTION_DOWN:
{
startX=(int)motionEvent.getRawX();
startY=(int)motionEvent.getRawY();
break;
}
case MotionEvent.ACTION_MOVE:
{
currentX=(int)motionEvent.getRawX();
currentY=(int)motionEvent.getRawY();
float diffX=currentX-startX;
float diffY=currentY-startY;
startX=currentX;
startY=currentY;
center.x+=diffX;
center.y+=diffY;
orbit.draw(center,radius);
break;
}
}
return true;
}
}
DrawOrbit.java
public class DrawOrbit extends View
{
PointF center;
float radius[];
public DrawOrbit(Context context) {
super(context);
}
public DrawOrbit(Context context, #Nullable AttributeSet attrs) {
super(context, attrs);
}
public DrawOrbit(Context context, #Nullable AttributeSet attrs, int defStyleAttr) {
super(context, attrs, defStyleAttr);
}
#Override
protected void onDraw(Canvas canvas)
{
super.onDraw(canvas);
paint.setColor(Color.BLACK);
paint.setStrokeWidth(2);
paint.setStyle(Paint.Style.STROKE);
int len=radius.length;
for(int a=0;a<len;a++)
{
canvas.drawCircle(center.x,center.y,radius[a],paint);
}
}
public void draw(PointF center, float radius[])
{
this.center=center;
this.radius=radius;
invalidate();
requestLayout();
}
}
What I want to do is that the circles should appear one by one. First the inner most circle then the next one after some delay then the next and so on. The same effect should be seen when the screen is dragged. How can I achieve this? Any help will be appreciated.
What you're looking for is a timeout. I'd suggest creating a new thread to draw everything, and start by drawing the first circle, have the method look like this:
public void drawCircle() {
//Draw logic
TimeUnit.SECONDS.sleep(3);
drawNextCircle();
}
//Assuming on java 1.8+
Thread thread = new Thread() => {
drawCircle();
}
What this will do is have the thread sleep for 3 seconds, and then continue normal operation after the time period is up. You can change it to other measurements of time such as TimeUnit.MILLISECONDS or TimeUnit.MINUTES as well.
edit: You likely won't want this in your main thread, as it will stop the ENTIRE application from working for however long you put the thread on timeout, so it's almost essential for this to work that you put it in a separate thread.
edit 2: It would make more sense to add a separate util method to timeout and then call another method via reflection than the above code, but the same code would be needed.
Figured out the answer to my own question. I had to use a Handler, multiplied the delay with the for-loop variable and made 1 then 2 then so on.. circles to get the desired effect. Here is the code.
MainActivity.java
public class MainActivity extends AppCompatActivity implements View.OnTouchListener
{
PointF center;
center.x=500;center.y=500;
float radius[]={100,200,300,400,500};
DrawOrbit orbit;
int startX=0,startY=0,currentX=0,currentY=0;
Handler handler1 = new Handler();
#Override
protected void onCreate(Bundle savedInstanceState)
{
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
RelativeLayout layout=(RelativeLayout)findViewById(R.id.scrollableSpace);
for (int a = 0; a<radius.length ;a++)
{
final int index=a;
handler1.postDelayed(new Runnable() {
#Override
public void run() {
orbit.draw(center,radius,index);
}
}, 300 * a);
}
layout.setOnTouchListener(this);
}
#Override
public boolean onTouch(View v, MotionEvent motionEvent)
{
final int action= motionEvent.getAction();
switch(action & MotionEvent.ACTION_MASK)
{
case MotionEvent.ACTION_DOWN:
{
startX=(int)motionEvent.getRawX();
startY=(int)motionEvent.getRawY();
break;
}
case MotionEvent.ACTION_MOVE:
{
currentX=(int)motionEvent.getRawX();
currentY=(int)motionEvent.getRawY();
float diffX=currentX-startX;
float diffY=currentY-startY;
startX=currentX;
startY=currentY;
center.x+=diffX;
center.y+=diffY;
break;
}
case MotionEvent.ACTION_UP:
{
for (int a = 0; a<radius.length ;a++)
{
final int index=a;
handler1.postDelayed(new Runnable() {
#Override
public void run() {
orbit.draw(center,radius,index);
}
}, 300 * a);
}
break;
}
}
return true;
}
}
DrawOrbit.java
public class DrawOrbit extends View
{
PointF center;
float radius[];
int index;
public DrawOrbit(Context context) {
super(context);
}
public DrawOrbit(Context context, #Nullable AttributeSet attrs) {
super(context, attrs);
}
public DrawOrbit(Context context, #Nullable AttributeSet attrs, int defStyleAttr) {
super(context, attrs, defStyleAttr);
}
#Override
protected void onDraw(Canvas canvas)
{
super.onDraw(canvas);
paint.setColor(Color.BLACK);
paint.setStrokeWidth(2);
paint.setStyle(Paint.Style.STROKE);
int len=radius.length;
for(int a=0;a<index;a++)
{
canvas.drawCircle(center.x,center.y,radius[a],paint);
}
}
public void draw(PointF center, float radius[],int index)
{
this.center=center;
this.radius=radius;
this.index=index;
invalidate();
requestLayout();
}
}
I am developing in Android Canvas.
But the onDraw() has not called after invalidate();
In the layout.xml
<?xml version="1.0" encoding="utf-8"?>
<LinearLayout
xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:app="http://schemas.android.com/apk/res-auto"
xmlns:tools="http://schemas.android.com/tools"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:orientation="vertical"
tools:context="com.wen.MainActivity">
<com.wen.Drawpath_view
android:id="#+id/view_path"
android:layout_weight="0.7"
android:layout_width="match_parent"
android:layout_height="0dp"/>
<RelativeLayout
android:layout_weight="0.3"
android:layout_width="match_parent"
android:layout_height="0dp">
<Button
android:id="#+id/test"
android:layout_width="wrap_content"
android:layout_height="wrap_content"/>
</RelativeLayout>
</LinearLayout>
In the MainActivity.java
public class MainActivity extends AppCompatActivity implements View.OnClickListener{
private static final String TAG = "MainActivity";
private Drawpath_view View_path;
private Button test_btn;
#Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
View_path = findViewById(R.id.view_path);
View_path = new Drawpath_view(MainActivity.this);
test_btn = findViewById(R.id.test);
test_btn.setOnClickListener(this);
View_path.getViewTreeObserver().addOnPreDrawListener(new ViewTreeObserver.OnPreDrawListener() {
#Override
public boolean onPreDraw() {
getWindow().setSoftInputMode(WindowManager.LayoutParams.SOFT_INPUT_ADJUST_RESIZE);
View_path.getViewTreeObserver().removeOnPreDrawListener(this);
View_path.setMinimumHeight(View_path.getHeight());
View_path.setMinimumWidth(View_path.getWidth());
return true;
}
});
}
#Override
public void onClick(View v) {
if (v.getId() == R.id.test){
View_path.Testdraw();
}
}
}
In the customer View.
public class Drawpath_view extends View {
private static final String TAG = "view";
private Paint paint;
private Path path = new Path();
public Drawpath_view(Context context) {
super(context);
setWillNotDraw(false);
initView();
}
public Drawpath_view(Context context, AttributeSet attrs) {
super(context, attrs);
}
private void initView() {
paint = new Paint();
paint.setAntiAlias(true);
paint.setDither(true);
paint.setStyle(Paint.Style.STROKE);
paint.setColor(Color.GREEN);
paint.setStrokeWidth(3);
}
public void Testdraw(){
Log.d(TAG,"draw");
path.moveTo(60,60);
path.lineTo(460,460);
invalidate();
}
#Override
protected void onDraw(Canvas canvas) {
Log.d(TAG,"onDraw");
super.onDraw(canvas);
canvas.drawPath(path,paint);
}
}
When I click the button in Activity. It will call
View_path.Testdraw();
But the onDraw in Drawpath_view has not been called. Did I missing something ?
Thanks in advance.
You can remove View_path = new Drawpath_view(MainActivity.this); from your code,
as you already attached view in xml.
By default all ViewGroup sub-classes do not call their onDraw method, you will enable it by calling setWillNotDraw(false) on View_path.
Add setWillNotDraw(false); initView();
into the second constructor as well.
I've created a custom spinner, because again and again, I found that I wanted to make sure that the onItemSelectedListener wasn't triggered when I set my Spinner's initial selection or set a new custom adapter. I only want it triggered when a user actually selects an item.
But for some reason (I'm at a complete loss as to why), my custom spinner doesn't respond to touch events. It's as if it's disabled, even though I've debugged and seen that it's perfectly enabled. But for some reason, my little spinner won't open. Can anyone help me understand why?
Here's the xml:
<LinearLayout
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_weight="1"
android:layout_marginTop="#dimen/default_margin"
android:orientation="horizontal">
<my.app.custom.view.MySpinner
android:id="#+id/dog_or_cat_toggle"
android:layout_width="0dp"
android:layout_height="wrap_content"
android:layout_weight="2"
android:layout_margin="0dp"
android:textAlignment="center"
android:gravity="center_vertical|center"
android:padding="0dp"
android:entries="#array/dog_or_cat"
android:spinnerMode="dropdown"
android:background="#drawable/top_to_bottom_gray_gradient"/>
...
</LinearLayout>
And my Custom Spinner:
/* A Spinner dispatches an onItemSelected event when the View is initialized, before the user ever makes a selection.
* This class allows listeners for just the initial selection, just user selections, or both. */
public class MySpinner extends Spinner {
private boolean initialized = false;
private OnItemSelectedListener onItemSelectionInitializedListener;
private OnItemSelectedListener onItemSelectedByUserListener;
private OnItemSelectedListener onItemSelectedListener;
public MySpinner(Context context, AttributeSet attrs) {
this(context, attrs, 0);
}
public MySpinner(Context context, AttributeSet attrs, int defStyleAttr) {
this(context, attrs, defStyleAttr, 0);
}
public MySpinner(Context context, AttributeSet attrs, int defStyleAttr, int defStyleRes) {
super(context, attrs, defStyleAttr, defStyleRes);
this.initializeMySpinner();
}
public void setOnItemSelectionInitializedListener(OnItemSelectedListener onItemSelectionInitializedListener) {
this.onItemSelectionInitializedListener = onItemSelectionInitializedListener;
}
public void setOnItemSelectedByUserListener(OnItemSelectedListener onItemSelectedByUserListener) {
this.onItemSelectedByUserListener = onItemSelectedByUserListener;
}
#Override
public void setOnItemSelectedListener(OnItemSelectedListener onItemSelectedListener) {
this.onItemSelectedListener = onItemSelectedListener;
}
#Override
public void setAdapter(SpinnerAdapter adapter) {
this.initialized = false;
super.setAdapter(adapter);
}
private void initializeMySpinner() {
super.setOnItemSelectedListener(new OnItemSelectedListener() {
#Override
public void onItemSelected(AdapterView<?> parent, View view, int position, long id) {
if(!initialized) {
if(onItemSelectionInitializedListener != null) onItemSelectionInitializedListener.onItemSelected(parent, view, position, id);
if(onItemSelectedListener != null) onItemSelectedListener.onItemSelected(parent, view, position, id);
initialized = true;
} else {
if(onItemSelectedListener != null) onItemSelectedListener.onItemSelected(parent, view, position, id);
if(onItemSelectedByUserListener != null) onItemSelectedByUserListener.onItemSelected(parent, view, position, id);
}
}
#Override
public void onNothingSelected(AdapterView<?> parent) {
if(!initialized) {
if(onItemSelectionInitializedListener != null) onItemSelectionInitializedListener.onNothingSelected(parent);
if(onItemSelectedListener != null) onItemSelectedListener.onNothingSelected(parent);
initialized = true;
} else {
if(onItemSelectedListener != null) onItemSelectedListener.onNothingSelected(parent);
if(onItemSelectedByUserListener != null) onItemSelectedByUserListener.onNothingSelected(parent);
}
}
});
}
}
Don't call one constructor from another. Instead, call super() constructor from each one.
I have faced the same issue some time back and this trick worked, but I'm not sure about the reason.
I'm not getting any errors when I run my code, but nothing happens I touch the screen. The value of the global variable select should change but nothing happens.
Here is the code
public class NonmultiplierSixGame extends AppCompatActivity {
#Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_nonmultiplier_six_game);
}
}
activity_nonmultiplier_six_game:
<?xml version="1.0" encoding="utf-8"?>
<android.support.constraint.ConstraintLayout
xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:app="http://schemas.android.com/apk/res-auto"
xmlns:tools="http://schemas.android.com/tools"
android:layout_width="match_parent"
android:layout_height="match_parent"
tools:context="com.example.alexandermain.example_5.NonmultiplierSixGame">
<com.example.alexandermain.example_5.views.NonmultiplierSixView
android:layout_width="match_parent"
android:layout_height="match_parent"
android:id="#+id/nonmultiplierSixView"
android:background="#color/colorPrimary"
/>
</android.support.constraint.ConstraintLayout>
NonmultiplierSixView class:
public class NonmultiplierSixView extends View implements View.OnTouchListener{
#Override
protected void onDraw(Canvas canvas){
//bunch of shapes
}
#Override
public boolean onTouch(View v, MotionEvent event) {
switch(event.getAction()) {
case MotionEvent.ACTION_DOWN:
Globals.SetSelect(1);
break;
case MotionEvent.ACTION_UP:
Globals.SetSelect(2);
break;
}
return true;
}
public NonmultiplierSixView(Context context, AttributeSet attrs) {
super(context, attrs);
}
}
Edit:
Here is the Globals class
public class Globals {
public static int select=-2;
public static void SetSelect(int t) {
select = t;
}
public static int GetSelect() {
return(select);
}
}
When you implement OntouchListener, you need to setThe listener on your Context.
Simply add it in your constructor:
It should be like this:
public NonmultiplierSixView(Context context, AttributeSet attrs) {
super(context, attrs);
setOnTouchListener(this);
}
I am a pretty inexperienced programmer but I created onTouch listener earlier today which works fine,
Try to change the onTouch method to something like this:
public boolean onTouch(View v, MotionEvent event) {
switch (event.getAction()){
case MotionEvent.ACTION_DOWN:
Globals.SetSelect(1);
return false;
case MotionEvent.ACTION_UP:
Globals.SetSelect(2);
return false;
}
return false;
}
I'm sorry in advance if it doesn't work for you..
There is no need to implements View.OnTouchListener, View class already have its onTouchEvent(MotionEvent event) method . Try this:
#Override
public boolean onTouchEvent(MotionEvent event) {
switch (event.getAction()){
case MotionEvent.ACTION_DOWN:
break;
case MotionEvent.ACTION_MOVE:
break;
case MotionEvent.ACTION_UP:
break;
}
return true;
}
It will definately work. and check this also.
The onDraw method repeats continuously.
So maybe, even if you touch your object, it is everytime redrawn to its initial position because of the onDraw.
So what you wanna do is for example set a boolean isPostionned, so your object will be drawn on initial position only 1 time.
so the boolean will be initially false, and then you set it true once your item is drawn.
For example: declare in your NonmultiplierSixView class
boolean isPositionned = false;
than in onDraw:
if (!isPositionned){
//code to position you object
isPositionned = true;
}
(This is in case your intention is to move the shape)
I put together a TextView class utilizing some different suggestions I've seen and wrote this class to display a TextView inside of a circle. The circle comes out great, but the text appears slightly above the center of the circle.
I can't figure out what's causing this. Here's my code:
CircularTextView
public class CircularTextView extends AppCompatTextView {
private ShapeDrawable backgroundDrawable;
private OvalShape ovalShape;
private int backgroundColor;
public CircularTextView(Context context) {
super(context);
backgroundColor = ContextCompat.getColor(context, R.color.color_circle_test_solid);
allocateShapes();
}
public CircularTextView(Context context, AttributeSet attrs) {
super(context, attrs);
backgroundColor = ContextCompat.getColor(context, R.color.color_circle_test_solid);
allocateShapes();
}
public CircularTextView(Context context, AttributeSet attrs, int defStyleAttr) {
super(context, attrs, defStyleAttr);
backgroundColor = ContextCompat.getColor(context, R.color.color_circle_test_solid);
allocateShapes();
}
//Source https://stackoverflow.com/questions/25203501/android-creating-a-circular-textview/34685568#34685568
#Override
protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
super.onMeasure(widthMeasureSpec, heightMeasureSpec);
int h = this.getMeasuredHeight();
int w = this.getMeasuredWidth();
int r = Math.max(w, h);
setMeasuredDimension(r, r);
}
#Override
protected void onDraw(Canvas canvas) {
super.onDraw(canvas);
backgroundDrawable.setShape(ovalShape);
backgroundDrawable.getPaint().setColor(backgroundColor);
setBackground(backgroundDrawable);
}
private void allocateShapes(){
backgroundDrawable = new ShapeDrawable();
ovalShape = new OvalShape();
}
public void setBackgroundColor(int color){
backgroundColor = color;
invalidate();
}
}
TestCircleTextViewActivity
public final class TestCircleTextViewActivity extends BaseActivity {
#BindView(R.id.circle_text)
CircularTextView circleText;
#Override
protected void onCreate(#Nullable Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_test_circular_textview);
ButterKnife.bind(this);
int circleColor = ContextCompat.getColor(this, R.color.color_circle_test_solid);
circleText.setBackgroundColor(circleColor);
}
}
activity_test_circular_textview.xml
<?xml version="1.0" encoding="utf-8"?>
<RelativeLayout xmlns:android="http://schemas.android.com/apk/res/android"
android:layout_width="match_parent"
android:layout_height="match_parent">
<com.thinkbubble.app.ui.view.CircularTextView
android:layout_height="wrap_content"
android:layout_width="wrap_content"
android:id="#+id/circle_text"
android:layout_gravity="center"
android:layout_centerInParent="true"
android:padding="4dp"
android:text="Keyword">
</com.thinkbubble.app.ui.view.CircularTextView>
</RelativeLayout>
Use android:gravity="center" for you TextView to make text center in circle