My application consists of a MapActivity, which loads an XML view (map_layout), containing a MapView and a CustomView which extends ImageView.
I use the following code to setup my MapActivity:
#Override
public void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.map_layout);
initialise();
}
Then in initialise, I retrieve the MapView and CustomView from the XML file using findViewById(...) as usual. Within the onMeasure(...) function of my CustomView I use this.getWidth() and this.getHeight() to get its dimensions, which returns the correct results.
Then within my initialise() code I decided to check if GPS is enabled (using code similar to the first answer here: How do I find out if the GPS of an Android device is enabled), showing an AlertDialog if GPS is disabled.
The problem is now that the onMeasure(...) function of my CustomView no longer works correctly, and this.getWidth() and this.getHeight() both return 0 if the AlertDialog gets displayed.
Why is this the case?
Thanks for your help in advance, if my question is unclear I'll edit it to add the full code when I have chance.
Okay, I needed to get the height and width within onLayout() instead of onMeasure() for some reason.
Related
I recently decided to update my older apps and bring them a little more up to date. In doing so I noticed an odd problem when running them on my new handset (Android O / API Level 29).
My App is essentially a single Activity using OpenGL. This is basically what happens when the app is first loaded...
Create Layout, Create a Splashscreen View, create custom GLSurfaceView
Add GLSurfaceView to Layout
Add Splashscreen to Layout
Start AsyncTask to load resources and do setup ops
GLSurfaceView dismisses Splashscreen to reveal renderer...
So, it all works perfectly well on my older devices (3 x handsets, 1 x tablet all running various versions of Android no higher than Lollipop).
However, on my Android 10 handset, all I get is a blank screen while the app starts. Just to be clear, there are no errors, the app itself works perfectly fine. After the usual startup time, the blank screen is dismissed and the app continues, it's just that the 'splashscreen' has now become a "blankscreen".
But Android Studio Layout Inspector tells a different story
Indeed, if I open the Layout Inspector in Android Studio, oddly it shows exactly what I would expect... the splashscreen with its text/graphics, but not on the actual device...
Some test code
In the following test code I replaced the resource loading / setup with a simple Thread.sleep just to create a longer delay so I could more easily inspect the output in Android Studio. This does still exhibit the problem...
#Override
protected void onCreate(Bundle savedInstanceState) {
int[] deviceResolution = getDeviceResolution();
width = deviceResolution[0];
height = deviceResolution[1];
layout = new RelativeLayout(this);
splash = new Splash(getApplication(), width, height);
myGLView = new CustomGLSurfaceView(this.getApplication());
layout.addView(myGLView);
layout.addView(splash);
setContentView(layout);
loadResource = new LoadResources(newBundle);
loadResources.execute();
super.onCreate(savedInstanceState);
}
class LoadResources AsyncTask<Void, Void, Void> {
Bundle savedBundle;
LoadResources(Bundle savedBundle){
this.savedBundle=savedBundle;
}
#Override
protected void onPreExecute() {
super.onPreExecute();
}
#Override
protected Void doInBackground(Void... params) {
// Simulate some long-running task here. While this is happening, splashscreen should be visible. And on older devices, it is. Not on Android O handset (but OK in Android Studio Layout Inspector).
try {
Thread.sleep(30000);
} catch (InterruptedException e) {
e.printStackTrace();
}
// Would typically make my calls here to load resources, and complete other setup tasks here
return null;
}
#Override
protected void onPostExecute(Void result) {
super.onPostExecute(result);
}
}
** Other observations **
I know that Async is now deprecated and I'm also aware of other Splashscreen tecnhiques such as using a dedicated Activity. However due to the setup/complexity of the apps and given that it doesn't 'break' them per-se, I don't want to have to refactor the entire codebase (just yet anyway) any new apps going forward will use a different system for the splashscreen. What I'm really trying to understand here is why this now doesn't work as it used to. Is this a bug? Or a behavioural change that happened in an Android version somewhere down the line? Or maybe it's just me doing something wrong. If anyone has come across it, I'd be really interested to know of a fix/workaround.
** Other things I've tried **
In the above example, if I simply don't add 'myGLView' then the splashscreen shows, but of course the app then won't work. So I though about adding it in onPostExecute and then bringing the splashscreen view back to the front with splash.bringToFront(). This kind of works, but is messy. The splashscreen only shows for a brief second, and on devices that show it correctly there is a 'glitch' as the correctly displayed splashscreen is overlayed with the GL Renderer and then bought back to the front.
You are adding a view to an object which is not currently set in an Activity.
The better approach would be calling setContentView() first as well as the super method, then adding the multiple views in it.
So in your case, it'll be like:
super.onCreate(savedInstanceState);
setContentView(layout);
layout = new RelativeLayout(this);
int[] deviceResolution = getDeviceResolution();
width = deviceResolution[0];
height = deviceResolution[1];
layout = new RelativeLayout(this);
splash = new Splash(getApplication(), width, height);
myGLView = new CustomGLSurfaceView(this.getApplication());
layout.addView(myGLView);
layout.addView(splash);
loadResource = new LoadResources(newBundle);
loadResources.execute();
#Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
// get orientation
LinearLayout layout = isLandscape()
? (LinearLayout) this.findViewById(R.id.main_layout_l)
: (LinearLayout) this.findViewById(R.id.main_layout_p);
Log.i("info", "layout is " + layout.getId());
start();
run();
}
#Override
public void onConfigurationChanged(Configuration newConfig) {
super.onConfigurationChanged(newConfig);
Log.e("On Config Change",
(newConfig.orientation==Configuration.ORIENTATION_LANDSCAPE)
? "LANDSCAPE" : "PORTRAIT");
// added -- this fixes the background not showing
setContentView(R.layout.activity_main);
}
I have two layouts (two files, layout/activity_main.xml, layout-land/activity.xml) for both orientations.
What is to be displayed is slightly different depending on the orientation
ie: portrait won't have some extra panels and some stuff has to be moved around.
I need to access the view programmatically.
When accessing 'layout' I get a NullPointerException
each layout orientation has different ids ending in "_p" or "_l" in the xml file to differentiate whether portrait or landscape, when accessing them in java.
Both xml files are currently LinearLayout->TextView
Either way I cant get them to draw
I am using a thread and a clock to handle the drawing calls. Like in a game.
This is needed for doing some various custom animations.
I don't even think the background is drawing.
If I change from LinearLayout and call the TextView directly by its id
it runs without error, but nothing draws.
Try to remove android:configChanges attribute.
It'd be better not to use android:configChanges attribute.
Please refer to Android Developers Site in details
Caution: Handling the configuration change yourself can make it much more difficult to
use alternative resources, because the system does not automatically apply them for you. This technique should be considered a last resort when you must avoid restarts due to a configuration change and is not recommended for most applications.
https://developer.android.com/guide/topics/resources/runtime-changes
I have to add a fragment over an Imageview, but my ImageView goes on top and covers the other views. All this is done in a RelativeLayout.
things must be done dynamically (no xml). I want that my activity has an image, which can be set DYNAMICALLY, and my fragment (added dynamically) should have transparent background, and if this is not true, i can set it to transparent at run time.
This is the code to create the image:
ImageView img=new ImageView(this);
img.setImageResource(imgID);
img.setScaleType(ImageView.ScaleType.CENTER_CROP);
((ViewGroup)findViewById(android.R.id.content)).getRootView()).addView(img);
As you can see, the image works as background, i'm using a ImageView for its scaleType.
Is there a way to do so?
SOLUTION
solved, i added the image to the wrong container.
The function below, however, has been pretty useful. It has been taken from an other answer, and lets a view to get on the back of all the others.
public static void sendViewToBack(View child) {
final ViewGroup parent = (ViewGroup)child.getParent();
if (parent!=null) {
parent.removeView(child);
parent.addView(child, 0);
}
}
I'm attempting to implement accessibility on a few custom views for an Android app.
I've condensed what is done in the Google Authenticator app with no luck:
public class CardView extends RelativeLayout {
// ...
#Override
public boolean dispatchPopulateAccessibilityEvent(AccessibilityEvent event) {
event.setClassName(this.getClass().getName());
event.setPackageName(this.getContext().getPackageName());
event.getText().add("Card Test");
return true;
}
}
All TalkBack reports back is "Double-tap to select" when it's inside a ListView or ViewPager.
Does ViewPager override accessibility events?
What do I need to do in order to have TalkBack say "Card Test" inside ViewPagers and ListViews like I expect it to?
For current versions of Android, you need to set the content description of the view.
myView.setContentDescription("Card Test");
ListView and associated classes expect you to use the onItemSelectedListener instead of assigning an onClickListener to each View (and rightfully so).
If incorporating alanv's suggestion, try to convince android system to read out the content description
by either
If(accessibilityModeIsEnabled())//custom method that checks context.getSystemService(Context.ACCESSIBILITY_SERVICE).isEnabled()
myView.sendAccessibilityEvent(AccessibilityEvent.TYPE_VIEW_HOVER_ENTER);
or AccessibilityEvent.TYPE_VIEW_ACCESSIBILITY_FOCUSED.
or requestFocus
Above should done when myView is visible. May be during onMesasure when width and height are both positive
If list view is still unable to do so, then try doing the above tricks on the first element of list view. Accessibility in Android varies devices to device and not one strategy fits all
Is there any way to always show zoom controls in webview?
I found this: Always show zoom controls on a MapView
but that's for the mapview.
I want them to always be visible.
super.onCreate(savedInstanceState);
super.loadUrl("file:///android_asset/www/index.html");
WebSettings ws = super.appView.getSettings();
ws.setSupportZoom(true);
ws.setBuiltInZoomControls(true);
setDisplayZoomControls() is only available from API 11 (Android 3). So you can't even consider using it until the vast majority of Android devices are 3 or above - which will not be for some years :(
Not sure if this will work as I never tried, but I checked the reference in developer.android.com
ws.setDisplayZoomControls(true);
If this worked dont forget to best answer this answer.
Also check this out:
http://www.tutorialforandroid.com/2009/02/webview-with-zoomcontrols-in-android.html
good tutorial. (Debunks my theory)
None of the above worked for my. Only when I start draging the webview content the controls show up the first time. So what I did as a "quick fix" is in onStart() of my Fragment that holds the Dialog with the webview I call:
webview.invokeZoomPicker();
Example:
#Override
public void onStart(){
super.onStart();
DialogFragment dialog = this;
//...stuff
if(dialog.getDialog()!= null && dialog.getDialog().getWindow()!=null){
//...more stuff
View v = dialog.getView();
if(v!=null){
//invoke controls on start once, they stay active a few seconds:
WebView webview =v.findViewById(R.id.frag_dlg_WebView);
webview.invokeZoomPicker();
}
}
}
The controls light up only for a few seconds until they disappear again, by then the user in my case should have dragged the webview already.