So I've done a lot of searching but still can't seem to find the exact reason as to why my SurfaceView won't display. Here's a little background as to what I'm doing:
I have a Linear Layout that is set Horizontally. It contains an ImageView, then a vertical Linear Layout, and finally another ImageView. In the vertical Linear Layout, there are essential three things: another ImageView at the top, an extended SurfaceView (called MagnetView) and an ImageView below that.
Here's the xml:
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
android:layout_width="fill_parent"
android:layout_height="fill_parent"
android:orientation="horizontal" >
<!-- Left Magnetrak -->
<ImageView
android:layout_width="wrap_content"
android:layout_height="fill_parent"
android:src="#drawable/vectorparts_01"
android:adjustViewBounds="true" />
<!-- Middle Linear Layout -->
<LinearLayout
android:layout_width="0dip"
android:layout_height="fill_parent"
android:orientation="vertical"
android:layout_weight="1">
<!-- Top Bar with Lifes and Score counter -->
<RelativeLayout
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:adjustViewBounds="true">
<ImageView
android:adjustViewBounds="true"
android:layout_width="fill_parent"
android:layout_height="wrap_content"
android:src="#drawable/vectorparts_22"/>
<!-- Score counter -->
<TextView
android:id="#+id/myImageViewText"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_alignParentRight="true"
android:layout_marginRight="10dp"
android:layout_marginTop="5dp"
android:gravity="center"
android:text="000000000000"
android:textColor="#000000" />
<!-- Life1 -->
<ImageView
android:id = "#+id/life1"
android:adjustViewBounds="true"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:src="#drawable/vectorparts_13"
android:layout_alignParentLeft="true"
android:layout_marginLeft="5dp"
android:layout_marginTop="1dp"
android:gravity="center" />
<!-- Life2 -->
<ImageView
android:id = "#+id/life2"
android:adjustViewBounds="true"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:src="#drawable/vectorparts_13"
android:layout_toRightOf="#id/life1"
android:layout_marginTop="1dp"
android:layout_marginLeft="1dp"
android:gravity="center" />
<!-- Life3 -->
<ImageView
android:id = "#+id/life3"
android:adjustViewBounds="true"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:src="#drawable/vectorparts_13"
android:layout_toRightOf="#id/life2"
android:layout_marginTop="1dp"
android:layout_marginLeft="1dp"
android:gravity="center" />
</RelativeLayout>
<!-- SurfaceView -->
<LinearLayout
android:layout_width="fill_parent"
android:layout_height="fill_parent"
android:id="#+id/middlesurface">
</LinearLayout>
<!-- Bottom Bar -->
<RelativeLayout
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:adjustViewBounds="true">
<ImageView
android:adjustViewBounds="true"
android:layout_width="fill_parent"
android:layout_height="wrap_content"
android:src="#drawable/vectorparts_02"/>
</RelativeLayout>
</LinearLayout>
<!-- Right Magnetrak -->
<ImageView
android:layout_width="wrap_content"
android:layout_height="fill_parent"
android:src="#drawable/vectorparts_01"
android:adjustViewBounds="true" />
Essentially, I want the MagnetView to show through (or punch a hole) where I put it in the Layout. But it does not display. In fact, the only time my SurfaceView displays is when I set the activity's setContentView() to the SurfaceView explicitly, canceling out everything else.
Here is the actually Activity:
public class Magnetraks extends Activity {
MagnetView midSurf;
/** Called when the activity is first created. */
#Override
public void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
midSurf = new MagnetView(this);
LinearLayout midLL = new LinearLayout(this);
midLL.findViewById(R.id.middlesurface);
midLL.addView(midSurf);
setContentView(R.layout.main);
//Debugging purposes: run to see if middleSurface view is showing up when on its own.
//setContentView(midSurf);
}
#Override
protected void onResume() {
// TODO Auto-generated method stub
super.onResume();
midSurf.resume();
}
#Override
protected void onPause() {
// TODO Auto-generated method stub
super.onPause();
midSurf.pause();
}
Should I place the SurfaceView at the top of my layout xml? Are there more attributes I must set?
Can I just overlay the SurfaceView over everything else, make it translucent and draw what I need? Any help would be greatly appreciated, as I cannot seem to grasp how SurfaceViews work. It seems they are apart of my View hierarchy, but then documentation tells me they are something different entirely.
Thank you.
EDIT 04/17/2012
To offer a little more info:
My xml UI Designer shows a big box in the middle for my extended SurfaceView class, called MagnetView. I've outlined it in red. (Stackoverflow won't allow me to post images yet)
UI Designer view(http://24.media.tumblr.com/tumblr_m2mmqvBNwW1qcreoco1_500.jpg)
http://24.media.tumblr.com/tumblr_m2mmqvBNwW1qcreoco1_500.jpg
Here's the MagnetView (SurfaceView) class:
import android.content.Context;
import android.graphics.Canvas;
import android.graphics.PixelFormat;
import android.view.SurfaceHolder;
import android.view.SurfaceView;
public class MagnetView extends SurfaceView implements Runnable, SurfaceHolder.Callback{
Thread mThread;
SurfaceHolder mSurfaceHolder;
volatile boolean running = false;
//Creates new surface view as well as a new surfaceholder, which allows access to the surface
public MagnetView (Context context){
super(context);
mSurfaceHolder = getHolder();
mSurfaceHolder.addCallback(this);
//this.setZOrderOnTop(true);
//getHolder().setFormat(PixelFormat.TRANSLUCENT);
}
public void resume(){
running = true;
mThread = new Thread(this);
mThread.start();
}
public void pause(){
boolean retry = true;
running = false;
while(retry){
try {
mThread.join();
retry = false;
} catch (InterruptedException e) {
// TODO Auto-generated catch block
e.printStackTrace();
}
}
}
#Override
public void run() {
// TODO Auto-generated method stub
while(running){
if(mSurfaceHolder.getSurface().isValid()){
Canvas canvas = mSurfaceHolder.lockCanvas();
//... actual drawing on canvas
canvas.drawARGB(100, 255, 255, 80);
mSurfaceHolder.unlockCanvasAndPost(canvas);
}
}
}
#Override
public void surfaceChanged(SurfaceHolder holder, int format, int width,
int height) {
// TODO Auto-generated method stub
}
#Override
public void surfaceCreated(SurfaceHolder holder) {
// TODO Auto-generated method stub
this.resume();
}
#Override
public void surfaceDestroyed(SurfaceHolder holder) {
// TODO Auto-generated method stub
}
}
I am just putting down the changes.
Replace surface view in the xml with LinearLayout.
<LinearLayout
android:id="#+id/middleSurface"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
/>
Get an instance of linear layout
LinearLayout surface = (LinearLayout)findViewById(R.id.surface);
Add the surface view's instance to LinearLayout
surface.addView(new MagnetView(this));
2,3 steps should be performed after setContentView(R.layout.main);
I would examine the layout in the Eclipse UI designer to see if everything is placed as you want to. Surface Views are just ordinary views in regards to layout.
If I am not wrong, the way I perceived your query is: You want something to be displayed/rendered on top of the surface view along with the other view elements as you mentioned in the xml.
If you want a view on top of surface view, you need to extend it(AFAIK there is no other way). Then you need to override the onDraw() method to display what all things you want on the surface view. You also need to get an object of SurfaceHolder which gets the canvas.
Here is a good link showing, putting an imageview on top of surface view:
http://www.droidnova.com/playing-with-graphics-in-android-part-ii,160.html
Remove the surface view from the xml and place a linearlayout in place of that and associate an id with it.
Get an instance of linearlayout.
add child view i.e., magnet view in this case to the linearlayout using
addView()
This solved the problem.
Have you set the background of the SurfaceView? I found that if you set the background of the SurfaceView, it will not display what you draw on the secondary thread.
You could try this other way, move setContentView(R.layout.main); after super.onCreate(savedInstanceState);
super.onCreate(savedInstanceState);
setContentView(R.layout.main);
midSurf = new MagnetView(this);
LinearLayout midLL = new LinearLayout(this);
midLL.findViewById(R.id.middlesurface);
midLL.addView(midSurf);
Related
Here's a challenge for all you android master programmers out there! I am currently putting 2 surface views into a fragment (LeftPanel and MySurfaceView), below a 'banner' edit text area and with 1 button below the two surface views in a 'footer ' position.
The layout is described in the fragment3.xml file see below :-
<?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"
android:orientation="horizontal"
android:background="#eeeeee"
>
<EditText
android:id="#+id/textF3"
android:layout_width="wrap_content"
android:layout_height="60dp"
android:text=" Fragment 3 "
android:clickable="false"
android:cursorVisible="false"
android:focusable="false"
android:layout_alignParentTop="true"
android:layout_centerHorizontal="true"
android:background="#null"
/>
<LinearLayout
android:orientation="horizontal"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:layout_below="#+id/textF3"
android:layout_alignParentStart="true"
android:weightSum="1"
>
<com.fragmentnavigationtestbed.LeftPanel
android:id="#+id/leftPanel"
android:layout_width="wrap_content"
android:layout_height="633dp"
android:layout_weight="0.5"
/>
<com.fragmentnavigationtestbed.MySurfaceView
android:id="#+id/view"
android:layout_width="wrap_content"
android:layout_height="633dp"
android:layout_weight="0.5"
/>
</LinearLayout>
<Button
android:id="#+id/button32"
style="?android:attr/buttonStyleSmall"
android:layout_width="wrap_content"
android:layout_height="36dp"
android:text="GO BACK TO FRAGMENT 1"
android:onClick="goto_fragment1"
android:layout_alignParentBottom="true"
android:layout_alignParentStart="true"
/>
</RelativeLayout>
This fragment is inflated by the onCreateView() method in the Fragment3.java file where the 2 surfaceViews (SVs) are instantiated and have their inTouchListeners set inside the onActivityCreated Method.
#Override
public View onCreateView(LayoutInflater inflater, ViewGroup container,
Bundle savedInstanceState) {
// Inflate the layout for this fragment
return inflater.inflate(R.layout.fragment3, container, false);
}
#Override
public void onActivityCreated(Bundle savedInstanceState) {
super.onActivityCreated(savedInstanceState);
panel = new LeftPanel(this.getActivity());
panel.setOnTouchListener(this);
myView = new MySurfaceView(this.getActivity());
myView.setOnTouchListener(this);
}
I have a simple draw method (doDraw) which draws a square on a coloured background, I cannot get this to create any visible change to the SV LeftPanel, unless its within the onTouchEvent() method of the LeftPanel.java file and only when the user clicks the surface of the Leftpanel SV. (This problem applies to the other SV MySurfaceView as well !).
public void doDraw() {
Paint paint = new Paint();
paint.setColor(Color.GREEN);
holder = getHolder();
Canvas canvas = holder.lockCanvas();
if (canvas != null) {
canvas.drawColor(Color.CYAN);
canvas.drawRect(100, 100, 200, 200, paint);
holder.unlockCanvasAndPost(canvas);
}
}
If you don't click the SV you see just black.
I've tried putting the draw method in the Fragment3 onActivityCreated() method, and in LeftPanel's run() method, but though the method is evoked nothing happens on the SV. It only works in the onTouchEvent() method
As long as I click on my Leftpanel SV, I get my onDraw() method result!
My problem is I want to visibly display, the results of my OnDraw() method before any user action has occurred, can anyone give me some help here?
I've searched a lot of the S/O answers, but none go anywhere near this area.
I have a class called Panel like this:
public class Test extends Activity{
public void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(new Panel(this));
...
}
class Panel extends SurfaceView implements SurfaceHolder.Callback {
private LinearLayout layout;
public Panel(Context context) {
super(context);
layout = //get layout resource in layouts folder
}
public void onDraw(Canvas canvas) {
//I want to draw my xml layout
layout.draw(canvas);
}
}
I have tried to use LayoutInflater in order to get my layout:
LayoutInflater inflater = LayoutInflater.from(context);
layout = new LinearLayout(getApplicationContext());
inflater.inflate(R.layout.mylayout, layout);
... but I cant see anything on the screen, only a black background :S
as far as I know you can't draw a linear layout inside the SurfaceView you can draw anything you want in your SurfaceView if you want to add elements to your view like buttons or something like that I recommend using the XML and putting all your elements there. Something like this:
<LinearLayout
xmlns:android="http://schemas.android.com/apk/res/android"
android:layout_width="fill_parent"
android:layout_height="fill_parent"
android:orientation="vertical" >
<SurfaceView android:id="#+id/mySurfaceView"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:layout_weight="1">
</SurfaceView>
<Button android:id="#+id/button"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_alignParentRight="true"
android:layout_centerVertical="true"
android:text="myButton"/>
</LinearLayout>
and then you may set your content of your view with setContentView(R.layout.my_xml);
My problem is communication between custom GameView (extends SurfaceView) and TextView: I want to set TextView's text from inside of the GameView.
In main activity i'm using this layout file, it should explain structure of my app:
<?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"
>
<RelativeLayout
android:layout_width="fill_parent"
android:layout_height="wrap_content"
android:background="#00ff00"
>
<TextView
android:id="#+id/scoreTV"
android:layout_alignParentLeft="true"
android:layout_centerVertical="true"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="score: 0"
/>
<Button
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="back"
android:layout_alignParentRight="true" />
</RelativeLayout>
<org.gk.grApp.GameView
android:id="#+id/gameplayScreen"
android:layout_width="fill_parent"
android:layout_height="fill_parent"
/>
</LinearLayout>
I can't change TextView's text in my GameView object, because it's impossible to touch UI thread from another.
Handler doesn't work too, because i can't give a handler's reference to GameView's constructor, that is performed after loading this xml file (a read about default constructor for xml files eg here How can I use GLSurfaceView in a LinearLayout together with other Views, such as TextView or Button?).
Do you have any idea what I should do now? Maybe my deduction is wrong, so please, tell me about this.
EDIT: I changed my xml file, instead of GameView I have now:
<LinearLayout
android:id="#+id/gameplayScreen"
android:layout_width="fill_parent"
android:layout_height="fill_parent"
>
</LinearLayout>
Also I added an argument (third) into constructor's signature:
public GameView(Context context, AttributeSet as, Handler h) { ... }
and changed my onCreate in GameplayActivity:
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.gameplay);
LinearLayout ll = (LinearLayout)findViewById(R.id.gameplayScreen);
GV = new GameView(this, null, scoreHandler);
ll.addView(GV);
}
It works, now I can set TextView's text but after clicking on the back button another exception is thrown:
"Performing pause of activity that is not resumed: {org.gk.grApp/org.gk.grApp.MainMenuActivity}". I just started searching information about this.
First make a reference to the TextView on the activity level:
TextView txv;
In onCreate assign this reference:
txv = (TextView) findViewById(R.id.MyTextView);
Then make a method in your Activity after onCreate like this:
public void setTextView(final String txt){
MyActivity.this.runOnUiThread(new Runnable() {
public void run() {
txv.setText(txt);
}
});
}
Then you make a call from your custom view:
((MyActivity) getContext()).setTextView(str);
I have this layout:
<?xml version="1.0" encoding="utf-8"?>
<RelativeLayout xmlns:android="http://schemas.android.com/apk/res/android"
android:layout_width="fill_parent"
android:layout_height="fill_parent" >
<com.components.game.GameView
android:id="#+id/game_id"
android:layout_width="fill_parent"
android:layout_height="fill_parent" />
<RelativeLayout
android:id="#+id/ChatLayout"
android:layout_width="fill_parent"
android:layout_height="wrap_content"
android:layout_alignParentBottom="true"
android:visibility="invisible"
android:focusable="true"
android:focusableInTouchMode="true" >
<Button
android:id="#+id/ChatCancelButton"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="X" />
<Button
android:id="#+id/ChatOkButton"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_alignParentRight="true"
android:text="OK" />
<EditText
android:id="#+id/ChatEditText"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_toLeftOf="#+id/ChatOkButton"
android:layout_toRightOf="#+id/ChatCancelButton"
android:maxLength="50"
android:singleLine="true" />
</RelativeLayout>
</RelativeLayout>
It's a RelativeLayout over a canvas. At start time it's invisible but when a user clicks a button the layout should become visible.
The problem is that it's not becoming visible. The layout is there but it's just not drawing it. If I press the position where the layout should appear it receives the event and opens the keyboard but it's not drawing the whole layout.
What is the problem?
If I set the RelativeLayout to visible at the beginning it works fine. it shows the layout and if I toggle between invisible and visible it works fine.
I made a workaround that almost always works.
I start the layout visible and than do that in the oncreate:
chatLayout.postDelayed(new Runnable() {
#Override
public void run() {
chatLayout.setVisibility(View.INVISIBLE);
}
}, 50);
But I don't like it and want to understand what's the problem.
The code:
It starts from a canvas button which send a message to a handler:
public void showInputLayout() {
Message.obtain(gameHandler, SHOW_INPUT_LAYOUT).sendToTarget();
}
In the handler:
case SHOW_INPUT_LAYOUT:
gameActivity.setChatVisibility(true);
break;
setChatVisibility:
public void setChatVisibility(boolean isVisible) {
int visible = isVisible ? View.VISIBLE : View.INVISIBLE;
chatLayout.setVisibility(visible);
if(isVisible){
chatEditText.setFocusable(true);
chatEditText.requestFocus();
}
}
Add a click listener to RelativeLayout and switch the visibility between GONE and VISIBLE. Try something like this:
int visibility = View.VISIBLE;
RelativeLayout layout = (RelativeLayout)findViewById(R.id.ChatLayout);
layout.setVisibility(visibility);
layout.setOnClickListener(new View.OnClickListener{
public void onClick(View v)
{
if(visibility == View.VISIBLE)
visibility = View.GONE;
else
visibility = View.VISIBLE;
v.setVisibility(visibility);
}
})
I ran into a similar issue recently, and for my case the problem was actually in the onDraw() method of the view underneath (should be com.components.game.GameView in your case). See if you can add calls to Canvas' getSaveCount(), save() and restoreToCount() in your drawing code, similar to this:
#Override
protected void onDraw(Canvas canvas) {
super.onDraw(canvas);
int saveCount = canvas.getSaveCount();
canvas.save();
// custom drawing code here ...
// use Region.Op.INTERSECT for adding clipping regions
canvas.restoreToCount(saveCount);
}
I believe what happened was that sometimes the framework set the clipping regions for the elements on top of our Canvas-drawing widget before our onDraw() method is called so we need to make sure that those regions are preserved.
Hope this helps.
I want to show two views in one activity. If I clicked on button in the first view I want to see the second and other way round.
The views should not have the same size as the screen so I want e.g. to center it, like you see in first.xml.
But if I add the views with
addContentView(mFirstView, new ViewGroup.LayoutParams(ViewGroup.LayoutParams.WRAP_CONTENT,
ViewGroup.LayoutParams.WRAP_CONTENT));
the views are not centered. They are shown at top left.
How can I use the xml settings to e.g. center it?
first.xml
<?xml version="1.0" encoding="utf-8"?>
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
android:orientation="vertical"
android:layout_height="wrap_content" android:layout_width="wrap_content"
android:background="#drawable/background"
android:layout_gravity="center"
android:minWidth="100dp"
android:minHeight="100dp"
android:paddingBottom="5dp"
>
<LinearLayout android:id="#+id/head"
android:layout_width="fill_parent"
android:layout_height="wrap_content"
android:orientation="horizontal">
<ImageButton android:id="#+id/first_button"
android:src="#drawable/show_second"
android:layout_gravity="center"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:background="#null" />
</LinearLayout>
second.xml same as first.xml but with
<ImageButton android:id="#+id/second_button"
android:src="#drawable/show_first"
... />
ShowMe.java
public class ShowMe extends Activity {
View mFirstView = null;
View mSecondView = null;
#Override
public void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
initFirstLayout();
initSecondLayout();
showFirst();
}
private void initFirstLayout() {
LayoutInflater inflater = getLayoutInflater();
mFirstView = inflater.inflate(R.layout.first, null);
getWindow().addContentView(mFirstView, new ViewGroup.LayoutParams(ViewGroup.LayoutParams.WRAP_CONTENT,
ViewGroup.LayoutParams.WRAP_CONTENT));
ImageButton firstButton = (ImageButton)mMaxiView.findViewById(R.id.first_button);
firstButton.setOnClickListener(new View.OnClickListener() {
public void onClick(View v) {
ShowMe.this.showSecond();
}
});
}
private void initSecondLayout() {
// like initMaxiLayout()
}
private void showFirst() {
mSecondView.setVisibility(View.INVISIBLE);
mFirstView.setVisibility(View.VISIBLE);
}
private void showSecond() {
mFirstView.setVisibility(View.INVISIBLE);
mSecondView.setVisibility(View.VISIBLE);
}}
Hope someone can help.
Thanks
Why don't you use setContentView(R.layout.yourlayout)? I believe the new LayoutParams you're passing in addContentView() are overriding those you defined in xml.
Moreover, ViewGroup.LayoutParams lacks the layout gravity setting, so you would have to use the right one for the layout you're going to add the view to (I suspect it's a FrameLayout, you can check with Hierarchy Viewer). This is also a general rule to follow. When using methods that take layout resources as arguments this is automatic (they might ask for the intended parent).
With this consideration in mind, you could set your layout params with:
FrameLayout.LayoutParams lp = new FrameLayout.LayoutParams(/* wrap wrap */);
lp.setGravity(Gravity.CENTER);
addContentView(mYourView, lp);
But I would recommend setContentView() if you have no particular needs.
EDIT
I mean that you create a layout like:
~~~/res/layout/main.xml~~~
<?xml version="1.0" encoding="utf-8"?>
<FrameLayout xmlns:android="....."
android:id="#+id/mainLayout"
android:layout_width="fill_parent"
android:layout_height="fill_parent"/>
then in your onCreate() or init...Layout():
setContentView(R.layout.main);
FrameLayout mainLayout = (FrameLayout)findViewById(R.id.mainLayout);
// this version of inflate() will automatically attach the view to the
// specified viewgroup.
mFirstView = inflater.inflate(R.layout.first, mainLayout, true);
this will keep the layout params from xml, because it knows what kind it needs. See reference.