Unable to display 2 instances of my custom SurfaceView

I created my own SurfaceView, which works fine on its own, but when I try to put two on separate tabs in a TabWidget, only one of them is displayed no matter which tab is selected, and it is always a SurfaceView that first appears when the application starts.

To illustrate the problem, I created a code sample that can be compiled to display the problem.

The SurfaceView below, called SurfaceViewCircle, simply creates a bitmap, draws a blue circle by default, and then displays it. There is a public changeColour () method that will change the color of a circle in a bitmap.

Secondly, I am creating an XML layout that simply contains one instance of SurfaceViewCircle.

In the Activity class, I create a TabWidget and host, etc. Then I inflate the above XML twice, but in one case, I change the color of the SurfaceViewCircle to red. After launching the application no matter which tab I select, the red circle ALWAYS displays EXCEPT for a brief instance when the application exits and the blue circle is displayed.

Can someone please indicate if I skipped a step when using SurfaceView?

This is the operation code:

public class TestActivity extends Activity { /** Called when the activity is first created. */ private TabHost mTabHost; private Context mTabHostContext; private View surfaceView1, surfaceView2; @Override public void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); /* * Setup tabs */ setContentView(R.layout.maintabs); setupTabHost(); //Prepares the TabHost from code rather than XML; mTabHost.getTabWidget().setDividerDrawable(R.drawable.tab_divider); //Sets a thin dividing line mTabHostContext = mTabHost.getContext(); surfaceView1 = LayoutInflater.from(mTabHostContext).inflate(R.layout.surfaceviewindependent, null); SurfaceViewCircle s = (SurfaceViewCircle)surfaceView1.findViewById(R.id.circle1); /* * Change the colour to red */ s.changeColour(getResources().getColor(R.color.red_square)); /* * Create a second layout containing SurfaceViewCircle but leave circle as default blue. */ surfaceView2 = LayoutInflater.from(mTabHostContext).inflate(R.layout.surfaceviewindependent, null); setupTab(surfaceView1,"SurfaceView1"); setupTab(surfaceView2,"SurfaceView2"); } private void setupTabHost() { mTabHost = (TabHost) findViewById(android.R.id.tabhost); mTabHost.setup(); } private void setupTab(final View view, final String tag) { View tabview = createTabView(mTabHost.getContext(), tag); // This creates a view to be used in the TAB only /* this creates the tab content AND applies the TAB created in the previous step in one go */ TabSpec setContent = mTabHost.newTabSpec(tag).setIndicator(tabview).setContent(new TabContentFactory() { public View createTabContent(String tag) {return view;} }); mTabHost.addTab(setContent); } private static View createTabView(final Context context, final String text) { View view = LayoutInflater.from(context).inflate(R.layout.tabs_bg, null); TextView tv = (TextView) view.findViewById(R.id.tabsText); tv.setText(text); return view; } } 

This is my custom SurfaceView:

 public class SurfaceViewCircle extends SurfaceView implements SurfaceHolder.Callback{ private Paint paint, circlePaint; private Bitmap bitmap = null; private int w; private int h; private int colour = 0; private Resources r = null; private _Thread t = null; private boolean surfaceIsCreated; public SurfaceViewCircle(Context context) { super(context); initialise(); } public SurfaceViewCircle(Context context, AttributeSet attrs) { super(context, attrs); initialise(); } public SurfaceViewCircle(Context context, AttributeSet attrs, int defStyle) { super(context, attrs, defStyle); initialise(); } private void initialise(){ r = getResources(); getHolder().addCallback(this); paint = new Paint(Paint.ANTI_ALIAS_FLAG); paint.setFilterBitmap(true); colour = R.color.blue_square; circlePaint = new Paint(Paint.ANTI_ALIAS_FLAG); circlePaint.setColor(r.getColor(colour)); circlePaint.setStyle(Style.FILL); circlePaint.setStrokeWidth(0.02f); t = new _Thread(getHolder()); } public void changeColour(int colour){ circlePaint.setColor(colour); if (surfaceIsCreated){ createBitmap(); } synchronized (t){ t.notify(); } } private Bitmap createBitmap(){ Bitmap b = null; b = Bitmap.createBitmap(w, h, Bitmap.Config.ARGB_8888); Canvas c = new Canvas(b); c.scale((float)w, (float)w); //Scales the background for whatever pixel size c.drawCircle(0.5f, 0.5f, 0.5f, circlePaint); //c.drawColor(r.getColor(colour)); return b; } public void onMeasure(int widthMeasureSpec, int heightMeasureSpec){ int width = measure(widthMeasureSpec); int height = measure(heightMeasureSpec); int d = Math.min(width, height); setMeasuredDimension(d,d); } private int measure(int measureSpec) { int result = 0; // Decode the measurement specifications int specMode = MeasureSpec.getMode(measureSpec); int specSize = MeasureSpec.getSize(measureSpec); return specSize; } @Override protected void onSizeChanged(int w, int h, int oldW, int oldH){ super.onSizeChanged(w, h, oldW, oldH); //synchronized (this){ this.w = Math.min(w, h); this.h = w; //} Bitmap b = createBitmap(); bitmap = b; Log.i("Square", "onSizeChanged() called."); } @Override public void surfaceChanged(SurfaceHolder holder, int format, int width, int height) { // TODO Auto-generated method stub } @Override public void surfaceCreated(SurfaceHolder holder) { Log.i("Panel", "surfaceCreated() called."); t.setRunning(true); t.start(); surfaceIsCreated = true; } @Override public void surfaceDestroyed(SurfaceHolder holder) { Log.i("Square", "surfaceDestroyed() called."); surfaceIsCreated = false; boolean retry = true; synchronized (t){ t.setRunning(false); t.notify(); } while (retry) { try { t.join(); retry = false; } catch (InterruptedException e) { // we will try it again and again... } } } private class _Thread extends Thread { private SurfaceHolder _surfaceHolder; private boolean _run = false; public _Thread(SurfaceHolder surfaceHolder) { _surfaceHolder = surfaceHolder; } public void setRunning(boolean run) { _run = run; } @Override public void run() { Canvas c = null; while (_run){ try { c = _surfaceHolder.lockCanvas(null); synchronized (_surfaceHolder) { synchronized(bitmap){ c.drawBitmap(bitmap, 0, 0, paint); } } } finally { // do this in a finally so that if an exception is thrown // during the above, we don't leave the Surface in an // inconsistent state if (c != null) { _surfaceHolder.unlockCanvasAndPost(c); } } synchronized(this){ try { wait(); } catch (InterruptedException e) { // TODO Auto-generated catch block } } } } } } 

Maintabs.xml file:

 <?xml version="1.0" encoding="utf-8"?> <LinearLayout xmlns:android="http://schemas.android.com/apk/res/android" android:orientation="vertical" android:layout_width="fill_parent" android:layout_height="fill_parent"> <TabHost xmlns:android="http://schemas.android.com/apk/res/android" android:id="@android:id/tabhost" android:layout_width="fill_parent" android:layout_height="fill_parent"> <LinearLayout android:orientation="vertical" android:layout_width="fill_parent" android:layout_height="fill_parent"> <TabWidget android:id="@android:id/tabs" android:layout_width="fill_parent" android:layout_height="wrap_content" android:layout_marginLeft="0dip" android:layout_marginRight="0dip" /> <FrameLayout android:id="@android:id/tabcontent" android:layout_width="fill_parent" android:layout_height="fill_parent" /> </LinearLayout> </TabHost> </LinearLayout> 

And surfaceviewindependent.xml:

  <?xml version="1.0" encoding="utf-8"?> <LinearLayout xmlns:android="http://schemas.android.com/apk/res/android" android:orientation="vertical" android:layout_width="match_parent" android:layout_height="match_parent"> <uk.co.androidcontrols.gauges.SurfaceViewCircle android:id="@+id/circle1" android:layout_width="match_parent" android:layout_height="match_parent" android:layout_weight="0.5" android:layout_margin="1dip"> </uk.co.androidcontrols.gauges.SurfaceViewCircle> </LinearLayout> 

I also noted that someone had a similar problem here.

Apologies for the poor formatting, but the code editor is almost impossible to use for large code quotes!

Additional Information

I tried to use setVisibility()' in onvisibilityChanged() , but this ultimately onvisibilityChanged() exception:

 protected void onVisibilityChanged(View changedView, int visibility){ super.onVisibilityChanged(changedView, visibility); changedView.setVisibility(visibility); Log.i("SurfaceViewCircle", "onVisibilityChanged() called."); } java.lang.IllegalThreadStateException: Thread already started. 

It seems that calling changedView.setvisibility() destroys the surface every time.

+6
source share
2 answers

It seems that I do not want to do with SurfaceView is not recommended: link

+1
source

I built a test project based on your code and surprisingly spent almost a couple of hours playing with it. I will quickly rip out my data now, as I have to hit the bag!

First of all, you definitely create two tabs, each tab has a separate instance of your custom SurfaceView . It's great.

Now, when the Activity starts first and the first tab is displayed, only the first SurfaceView initialized and has surfaceCreated() , and at that moment its Thread is executed.

When the second tab is selected, the second SurfaceView , which sends a createTabContent() request for it, is then initialized in the same way as the first. From now on, from this moment until the Activity SurfaceView off, both SurfaceView remain in their actual surface state. Switching between tabs never calls surfaceDestroyed() on a SurfaceView , and therefore surfaceCreated() never called again. In addition, after the first creation, 'onMeasure ()' is called again. Therefore, this tells me that both SurfaceView remain in the general View hierarchy. Both SurfaceViews ' SurfaceViews work, and if you did not have wait() , they will constantly try to display the video memory.

As you know, SurfaceView very unique in how it sits (or rather, doesn’t sit) in the View hierarchy. What seems to be happening here is that the first SurfaceView to be created is the one whose output is shown in the video memory, regardless of which tab is selected.

One thing I tried for the first time was that the second SurfaceView was significantly smaller than the first, with a proportionally smaller circle inside. When switching from the first tab (larger SurfaceView with a large red circle) to the second tab (smaller than SurfaceView with a smaller blue circle), I could see that the size of the visible SurfaceView reduced correctly, as if the second SurfaceView became visible, but instead of the visible smaller blue circle I only had most of the first large red circular circle of SurfaceView , but cut off by the smaller size of the second SurfaceView .

What I played in was the following two method calls:

 ((SurfaceView)surfaceView1.findViewById(R.id.circle1)).setVisibility(View.GONE); ((SurfaceView)view.findViewById(R.id.circle1)).bringToFront(); 

The latter, bringToFront() , seems to have achieved nothing. But by calling setVisibility(View.GONE) on the first SurfaceView , as soon as the second tab of the SurfaceView was selected, then it perfectly switched from the red circle to the blue.

What I think you need to try is finding the appropriate TabHost API TabHost for overriding, which will be called every when the tab is selected (possibly using TabHost.OnTabChangeListener )) and use this as the place to call setVisibility() , if necessary, on all SurfaceView to control which one is displayed on top.

+4
source

Source: https://habr.com/ru/post/915234/


All Articles