I want to check my webview is scrolled down or not - android

I have a WebView Fragment in my application, I want to check, the user scrolled the WebView down or not ,for example if the user scrolled down my WebView , I want show a Toast message that "you scrolled down" I'm trying this for the past 3 days, I didn't get any proper solution. I have seen many tutorials and discussions, none of them are working for me. Is there any one who can help ?
This is my fragment java code
public class tab1 extends Fragment {
public ProgressBar bar;
public FrameLayout frameLayout;
public tab1()
{
}
#Override
public View onCreateView(LayoutInflater inflater, ViewGroup container,
Bundle savedInstanceState) {
View rootView = inflater.inflate(R.layout.fragment_tab1, null);
final SwipeRefreshLayout swipe =(SwipeRefreshLayout)rootView.findViewById(R.id.swiperefresh1);
frameLayout=(FrameLayout) rootView.findViewById(R.id.frame1);
bar=(ProgressBar)rootView.findViewById(R.id.progressBar1);
final WebView view=(WebView) rootView.findViewById(R.id.webview);
bar.setMax(100);
view.loadUrl("http://facebook.com");
view.getSettings().setJavaScriptEnabled(true);
view.setWebViewClient(new MyWebViewClient());
view.setWebChromeClient(new WebChromeClient(){
public void onProgressChanged(WebView view1,int progress){
frameLayout.setVisibility(View.VISIBLE);
bar.setProgress(progress);
if (progress==100){
frameLayout.setVisibility(View.GONE);
swipe.setRefreshing(false);
}
super.onProgressChanged(view1,progress);
}
});
view.getSettings().setBuiltInZoomControls(true);
view.getSettings().setDisplayZoomControls(false);
bar.setProgress(0);
swipe.setOnRefreshListener(new SwipeRefreshLayout.OnRefreshListener() {
#Override
public void onRefresh() {
swipe.setColorSchemeResources(
R.color.pink, R.color.indigo, R.color.lime);
String webUrl = view.getUrl();
view.loadUrl(webUrl);
}
});
return rootView;
}
}
This is my xml file
<LinearLayout 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:orientation="vertical"
xmlns:app="http://schemas.android.com/apk/res-auto"
tools:context="com.hackerinside.jaisonjoseph.polysocial.tab1">
<FrameLayout
android:id="#+id/frame1"
android:layout_width="match_parent"
android:layout_height="3dp"
android:background="#android:color/transparent">
<ProgressBar
android:id="#+id/progressBar1"
style="?android:attr/progressBarStyleHorizontal"
android:layout_width="fill_parent"
android:layout_height="3dp"
android:background="#android:color/transparent"
android:foregroundGravity="top"
android:progressDrawable="#drawable/custom_progress"
android:progress="20"/>
</FrameLayout>
<android.support.v4.widget.SwipeRefreshLayout
xmlns:android="http://schemas.android.com/apk/res/android"
android:id="#+id/swiperefresh1"
android:layout_width="match_parent"
android:layout_height="match_parent">
<WebView
android:id="#+id/webview"
android:layout_width="match_parent"
android:layout_height="match_parent" >
</WebView>
</android.support.v4.widget.SwipeRefreshLayout>
Error message after adding answer
Cast from null to OnScrollChangeListener requires API level 23 (current min is 15) less... (Ctrl+F1)
This check scans through all the Android API calls in the application and warns about any calls that are not available on all versions targeted by this application (according to its minimum SDK attribute in the manifest). If you really want to use this API and don't need to support older devices just set the minSdkVersion in your build.gradle or AndroidManifest.xml files. If your code is deliberately accessing newer APIs, and you have ensured (e.g. with conditional execution) that this code will only ever be called on a supported platform, then you can annotate your class or method with the #TargetApi annotation specifying the local minimum SDK to apply, such as #TargetApi(11), such that this check considers 11 rather than your manifest file's minimum SDK as the required API level. If you are deliberately setting android: attributes in style definitions, make sure you place this in a values-vNN folder in order to avoid running into runtime conflicts on certain devices where manufacturers have added custom attributes whose ids conflict with the new ones on later platforms. Similarly, you can use tools:targetApi="11" in an XML file to indicate that the element will only be inflated in an adequate context

ok try this
view.setOnScrollChangeListener(new View.OnScrollChangeListener() {
#Override
public void onScrollChange(View v, int scrollX, int scrollY, int
oldScrollX, int oldScrollY) {
}
});
here view is your WebView object. :) its native method so it will work fine.

Related

Using a webview changed default language

My app default language is Spanish (es). I have introduced a webview in XML. Prior to this change, the language is Spanish. After adding the webview, it's automatically showing English. How can I fix this issue or problem?
Thanks for helping in advance.
I already used below code too.
WebSettings webSettings = myWebView.getSettings();
webSettings.setJavaScriptEnabled(true);
webSettings.setUserAgentString(String.valueOf(Locale.SPANISH));
<WebView
android:id="#+id/webView"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_marginStart="-5dp"
tools:ignore="WebViewLayout" />
You can dynamically add web view and initialize the language again after that.
llDynemic=(LinearLayout)findViewById(R.id.test);
WebView webView = new WebView(getContext());// webview in mainactivity
webView.setLayoutParams(new ViewGroup.LayoutParams(ViewGroup.LayoutParams.MATCH_PARENT,
ViewGroup.LayoutParams.WRAP_CONTENT));
webView.setBackgroundColor(Color.TRANSPARENT);
webView.getSettings().setJavaScriptEnabled(true);
webView.loadDataWithBaseURL(null, "<style>img{display: inline;height: auto;max-width: 100%;} a {color: #337ab7;}</style>" + newBody, "text/html", "UTF-8", null);
llDynemic.addView(webView);
// initialize the language here
and it will work
in activity have webView
#Override protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
init here language again then
setContentView(R.layout.activity_terms_of_use);
After a lot of testing and following the advices from the other answers, I've finally fixed the issue.
Issues with WebView:
WebView have to be loaded dynamically in the activity. Loading from XML file via Activity#setContentView() won't work for the first time and localisation of the entire activity will break. However, subsequent launches or Activity#recreate() will work as expected.
Even if WebView is loaded dynamically, it won't work for the first time. Again, subsequent launches or Activity#recreate() will work properly.
Considering the issues above, the solution involves loading WebView dynamically and then, the immediate recreation of the activity.
ActivityWithWebView.java
public class ActivityWithWebView extends AppCompatActivity {
private WebView webView;
private static boolean firstTime = true;
#Override
protected void onCreate(#Nullable Bundle savedInstanceState) {
setContentView(R.layout.activity_with_web_view);
setSupportActionBar(findViewById(R.id.toolbar));
ActionBar actionBar = getSupportActionBar();
if (actionBar != null) actionBar.setTitle(R.string.instructions);
// WebView has to be loaded dynamically to prevent in-app localisation issue.
webView = new WebView(this);
if (firstTime) {
// Recreate if loaded for the first time to prevent localisation issue.
recreate();
firstTime = false;
return;
}
LinearLayoutCompat webviewWrapper = findViewById(R.id.webview_wrapper);
webView.setLayoutParams(new ViewGroup.LayoutParams(ViewGroup.LayoutParams.MATCH_PARENT,
ViewGroup.LayoutParams.WRAP_CONTENT));
webviewWrapper.addView(webView);
// Do other works.
}
}
activity_with_web_view.xml
<?xml version="1.0" encoding="utf-8"?>
<androidx.coordinatorlayout.widget.CoordinatorLayout
xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:app="http://schemas.android.com/apk/res-auto"
android:layout_width="match_parent"
android:layout_height="match_parent">
<com.google.android.material.appbar.AppBarLayout
xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:app="http://schemas.android.com/apk/res-auto"
android:id="#+id/appbar"
android:layout_width="match_parent"
android:layout_height="wrap_content">
<androidx.appcompat.widget.Toolbar
android:id="#+id/toolbar"
android:layout_width="match_parent"
android:layout_height="?attr/actionBarSize"
android:background="?android:colorBackground" />
</com.google.android.material.appbar.AppBarLayout>
<androidx.appcompat.widget.LinearLayoutCompat
android:id="#+id/webview_wrapper"
android:layout_width="match_parent"
android:layout_height="match_parent"
app:layout_behavior="com.google.android.material.appbar.AppBarLayout$ScrollingViewBehavior" />
</androidx.coordinatorlayout.widget.CoordinatorLayout>

How to support dpad controls for RecyclerView

I am currently trying to port Android mobile app to Android TV. I have a RecyclerView that seems to be displaying correctly in my Android TV app. I am using linearLayout for my RecyclerView. But I don't seem to be able to navigate inside RecyclerView using dpad controls.
Any ideas?
Here is the concerned xml:
<android.support.v7.widget.RecyclerView xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:tools="http://schemas.android.com/tools"
android:id="#+id/RecyclerView"
android:layout_width="240dp"
android:layout_height="match_parent"
android:layout_gravity="left"
android:background="#color/nav_bg"
android:scrollbars="vertical"
tools:showIn="#layout/activity_content" />
Make root view of your item layout android:focusable="true"
Try setting android:descendantFocusability="afterDescendants" on the RecyclerView
What my code was having problems doing was giving focus to the first child of the recyclerview. Once it was there it seemed to handle dpad controls well.
Without seeing more of your code, this is the best suggestion. Please provide your activity and full xml if this doesn't work. Good Luck!
In your xml:
<!-- make sure this is an attribute for the layout that would
get focus before the recycler view -->
android:nextFocusDown="#+id/RecyclerView"
<android.support.v7.widget.RecyclerView xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:tools="http://schemas.android.com/tools"
android:id="#+id/RecyclerView"
android:layout_width="240dp"
android:layout_height="match_parent"
android:layout_gravity="left"
android:background="#color/nav_bg"
android:scrollbars="vertical"
tools:showIn="#layout/activity_content"
android:focusable="true"
/>
In your activity:
public static final int BANNER = 0;
public static final int RECVIEW1 = 1;
public static final int RECVIEW2 = 2;
private static int currFocus = 0;
//you will need to do something similar to this listener for all focusable things
RecyclerView.setOnFocusChangeListener(new View.OnFocusChangeListener() {
#Override
public void onFocusChange(View v, boolean hasFocus) {
Log.i("ONFOCUSCHANGE- reclist", "focus has changed I repeat the focus has changed! current focus = " + currFocus);
if(currFocus != RECVIEW1){
currFocus = RECVIEW1;
RecyclerView.getChildAt(0).requestFocus();
}
}
});

Use of setVisibility(View.INVISIBLE) with pre Lollipop Circular Reveal library

I'm working on a project in which I want to use the Circular Reveal effect as per the Material Design. Project has minSDK = 11, so for compatibility with pre-Lollipop devices, I'm using this library https://github.com/ozodrukh/CircularReveal
I've a fragment with a FloatingActionButton that, when tapped, will transform itself in the CardView, like described here FAB transformations.
Once the card is revealed it has a button to revert the animation, re-transforming the card into the FAB. Now my problem is this: let's say that a user tap the FAB and the CardView is revealed. Now the user rotate his device, so the activity resets the fragment. What I want to achieve is the card stay visible and revealed, while the FAB should be disabled and invisible. The problem is that if I simply use setVisibility(View.INVISIBLE) on my FAB it doesn't work (note that if I use getVisibility() on it just after setting it invisible, it correctly returns me the value 4 == View.INVISIBLE, but the fab is still visible). I've to wrap the setVisibility(...) call inside a postDelayed() with at least 50-100 ms of delay to make the fab invisible.
So my question is: am I doing things right or there's a better way to accomplish what I want (because it seems very ugly to me)?
Here's some code. This is my XML layout of the fragment:
<android.support.design.widget.CoordinatorLayout
xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:app="http://schemas.android.com/apk/res-auto"
android:layout_width="match_parent"
android:layout_height="match_parent">
<android.support.design.widget.AppBarLayout
android:id="#+id/my_appbar"
android:layout_width="match_parent"
android:layout_height="#dimen/toolbar_expanded_height"
android:theme="#style/ThemeOverlay.AppCompat.Dark.ActionBar">
<android.support.design.widget.CollapsingToolbarLayout
android:layout_width="match_parent"
android:layout_height="match_parent"
app:expandedTitleMarginEnd="64dp"
app:expandedTitleMarginStart="70dp"
app:layout_scrollFlags="scroll|exitUntilCollapsed">
<android.support.v7.widget.Toolbar
android:layout_width="match_parent"
android:layout_height="?attr/actionBarSize"
app:popupTheme="#style/ToolbarPopupTheme"
app:layout_collapseMode="pin"/>
</android.support.design.widget.CollapsingToolbarLayout>
</android.support.design.widget.AppBarLayout>
...
<io.codetail.widget.RevealFrameLayout
android:layout_width="match_parent"
android:layout_height="match_parent">
<include layout="#layout/my_fragment" />
</io.codetail.widget.RevealFrameLayout>
<android.support.design.widget.FloatingActionButton
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_margin="#dimen/fab_margin"
app:fabSize="mini"
app:layout_anchor="#+id/my_appbar"
app:layout_anchorGravity="bottom|left|start" />
</android.support.design.widget.CoordinatorLayout>
This is the XML layout of the included CardView:
<android.support.v7.widget.CardView
xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:card_view="http://schemas.android.com/apk/res-auto"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_margin="16dp"
android:layout_gravity="center"
card_view:cardCornerRadius="2dp"
android:visibility="invisible"
android:focusableInTouchMode="true">
<RelativeLayout android:layout_width="match_parent"
android:layout_height="match_parent">
...
<LinearLayout android:layout_width="match_parent"
android:layout_height="wrap_content"
android:orientation="horizontal"
android:gravity="end">
<Button android:layout_height="wrap_content"
android:layout_width="wrap_content"
android:text="Ok" />
<Button android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="Cancel" />
</LinearLayout>
</RelativeLayout>
</android.support.v7.widget.CardView>
And this is the code of my Fragment:
public class MyFragment extends Fragment {
private static final String CARD_OPEN_TAG = "CARD_OPEN_TAG";
public static MyFragment newInstance(){
return new MyFragment();
}
private int cardOpen;
private FloatingActionButton fabAddPublication;
private CardView card;
private Button cardCancel;
#Override
public View onCreateView(LayoutInflater inflater, ViewGroup container,
Bundle savedInstanceState) {
final View rootView = inflater.inflate(R.layout.fragment_magazines, container, false);
toolbar = (Toolbar) rootView.findViewById(R.id.layout);
...
// Initialize view status
if (savedInstanceState != null){
cardOpen = savedInstanceState.getInt(CARD_OPEN_TAG);
} else {
cardOpen = -1;
}
...
// Get FAB reference
fab = (FloatingActionButton) rootView.findViewById(R.id.fab_id);
// Get card reference
card = (CardView) rootView.findViewById(R.id.card_id);
editorPublication.getViewTreeObserver().addOnGlobalLayoutListener(new ViewTreeObserver.OnGlobalLayoutListener() {
#Override
public void onGlobalLayout() {
// Using this event because I need my card to be measured to move correctly fab at his center
if (cardOpen != -1){
// Move FAB to center of card
fab.setTranslationX(coordX); // WORKS
fab.setTranslationY(coordY); // WORKS
// fab.setVisibility(View.INVISIBLE) -> DOESN'T WORK, fab remain visible on top and at center of my card
// Ugly workaround
new Handler().postDelayed(new Runnable() {
#Override
public void run() {
// Hide FAB
fab.setVisibility(View.INVISIBLE);
}
}, 50); // Sometimes fails: if device/emulator use too much time to "rotate" screen, fab stay visible
// Remove listener
ViewTreeObserver obs = card.getViewTreeObserver();
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.JELLY_BEAN)
obs.removeOnGlobalLayoutListener(this);
else obs.removeGlobalOnLayoutListener(this);
}
}
});
if (editorOpen != -1){
fab.setEnabled(false); // WORKS
card.setVisibility(View.VISIBLE); // WORKS
}
// Get editors buttons reference
cardCancel = (Button) card.findViewById(R.id.card_cancel_id);
// Set FAB listener
fab.setOnClickListener(new View.OnClickListener() {
#Override
public void onClick(View v) {
// Explode FAB
explodeFab(fab, card); // This method trigger the reveal animation
cardOpen = card.getId();
}
});
// Set editors button listeners
cardCancel.setOnClickListener(new View.OnClickListener() {
#Override
public void onClick(View v) {
// Implode FAB
implodeFAB(fab, card); // This card reverts the reveal animation
cardOpen = -1;
}
});
...
return rootView;
}
#Override
public void onSaveInstanceState(Bundle outState){
super.onSaveInstanceState(outState);
...
outState.putInt(CARD_OPEN_TAG, cardOpen);
}
}
I don't think that what you're doing is correct and here's why:
1.
That circular reveal library you're using is incompatible with hardware acceleration on Android 11 to 17 (3.0 to 4.2). It's using Canvas.clipPath() - a method which was implemented in software only up to Android 4.3. It means that you have to turn hardware acceleration off or your app will crash on not supported OpenGL call.
The best way to clip layouts is to use Canvas.saveLayer()/restoreLayer(). It's the only way to support all devices, get antialiased image and support invalidate/layout/draw flow correctly.
2.
Relying on timers and layout listeners to change layouts means that something is not really working for you. Each change can be executed directly without waiting. You just have to find the right place for that piece of code.
Timers can also trigger when your app is in background. It means that it will crash while trying to access UI from Timer thread.
Maybe you're calling setVisibility(true) somewhere in the code and that's why your FAB is visible? Setting visibility is ok and should work without any delayed calls. Just save visibility as FAB's state and restore it after orientation change.
If you wish to position the FAB at the center of the toolbar, wrap them both in a FrameLayout (wrap_content) and position the FAB with layout_gravity="center". That should allow you to remove layout listener.
3.
support.CardView is broken and shouldn't be used at all. It looks and works a little bit different on Lollipop and on older systems. The shadow is different, the padding is different, content clipping doesn't work on pre-Lollipop devices, etc. That's why it's hard to get consistent, good results on all platforms.
You should consider using plain layouts for that purpose.
4.
Some animations are cool-looking, but hard to implement, doesn't give any value and quickly become irritating, because the user has to wait for the animation to finish each time he/she clicks the button.
I'm not saying no for your case, but you may consider removing the transformation and using a drop down menu, a bottom sheet, a static toolbar or a dialog for that purpose.

Android: Can't get YouTube Player API work inside of a fragment

I'm trying to get my app to play a YouTube video in a fragment, as This Official Documentation said that you can play YouTube videos in fragments.
But i can't get it done.
This is my code:
SingleArticleFragment:
public class SingleArticleFragment extends YouTubePlayerSupportFragment implements
YouTubePlayer.OnInitializedListener {
public static final String API_KEY = "my api key";
public static final String YOUTUBE_VIDEO_CODE = "_oEA18Y8gM0";
// YouTube player view
private YouTubePlayerView youTubeView;
#Override
public View onCreateView(LayoutInflater inflater, ViewGroup container,
Bundle savedInstanceState) {
View v = inflater.inflate(R.layout.article, container, false);
return v;
}
#Override
public void onViewCreated (View view, Bundle savedInstanceState) {
youTubeView = (YouTubePlayerView) getActivity().findViewById(R.id.youtube_view);
// Initializing video player with developer key
youTubeView.initialize(API_KEY, this);
}
#Override
public void onInitializationSuccess(YouTubePlayer.Provider provider, YouTubePlayer youTubePlayer, boolean b) {
if (!b) {
// loadVideo() will auto play video
// Use cueVideo() method, if you don't want to play it automatically
youTubePlayer.cueVideo(YOUTUBE_VIDEO_CODE);
// Hiding player controls
youTubePlayer.setPlayerStyle(YouTubePlayer.PlayerStyle.CHROMELESS);
}
}
#Override
public void onInitializationFailure(YouTubePlayer.Provider provider, YouTubeInitializationResult youTubeInitializationResult) {
if (youTubeInitializationResult.isUserRecoverableError()) {
youTubeInitializationResult.getErrorDialog(getActivity(), 1).show();
} else {
String errorMessage = "There was an error initializing the YouTubePlayer";
Toast.makeText(getActivity(), errorMessage, Toast.LENGTH_LONG).show();
}
}
}
And this is article.xml:
<?xml version="1.0" encoding="utf-8"?>
<ScrollView xmlns:android="http://schemas.android.com/apk/res/android"
android:layout_width="match_parent" android:layout_height="match_parent"
android:background="#color/white">
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
android:layout_width="match_parent" android:layout_height="wrap_content"
android:orientation="vertical">
<!-- Cover Video -->
<com.google.android.youtube.player.YouTubePlayerView
android:id="#+id/youtube_view"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_marginBottom="30dp" />
<!-- Article Cover Photo -->
<ImageView
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:id="#+id/single_article_cover_photo"
android:layout_gravity="center"
android:adjustViewBounds="true"
android:layout_weight=".14"/>
<!-- Article Title -->
<TextView
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:id="#+id/single_article_title"
android:layout_gravity="center"
android:textColor="#color/black"
android:textSize="20sp"
android:textStyle="bold"
android:padding="10dp"
android:layout_weight=".14"/>
</LinearLayout>
</ScrollView>
LogCat Errors:
android.view.InflateException: Binary XML file line #11: Error inflating class com.google.android.youtube.player.YouTubePlayerView
.
.
.
Caused by: java.lang.IllegalStateException: A YouTubePlayerView can only be created with an Activity which extends YouTubeBaseActivity as its context.
So what LogCat is telling me: YouTube only works in Activities!, but android documentation says otherwise.
Can anybody help here please ?
Thanks in advance.
You are mixing two different approaches.
You can either use a YouTubePlayerView together with a YouTubeBaseActivity or you can simply use a YouTubePlayerFragment/YouTubePlayerSupportFragment. Using a YouTubePlayerView inside a YouTubePlayerFragment is simply wrong.
If you use the first approach (view + activity), you need to place the view in your XML and then set the YouTubePlayer to play inside that view.
If you use the second approach, you simply need to load the fragment in an appropriate container, initialize the YouTubePlayer and play the video.
SOLUTION 1
Remove the YouTubePlayerView from your XML and your code and replace it with a FrameLayout that will contain the YouTubeSupportFragment. Then, use a ChildFragmentManager to load the YouTubeSupportFragment inside that FrameLayout.
SOLUTION 2
Simply make your Activity extend the YouTubeBaseActivity, leave everything else as it is.

How to add a sliding drawer a MapView/View that's created programmatically

I've tried to approach this from different angles without luck. Maybe asking a general question can help.
Technically I'm using osmdroid's MapView implementation, not Google's Maps API, but I think this question is a more general programmatic Views vs main_activity.xml defined views in onCreate.
Basically in my MainActivity if I onCreate a View, like MapView, then set it as the ContentView programmatically, I have to also programmatically add in any other Views I want to display in my app:
public void onCreate(final Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
this.mapView = new MapView(this, 256);
...
this.setContentView(this.mapView);
}
If I attempt to set the ContentView as activity_main, the MapView can't be adjusted onCreate. Maybe I'm missing something: (note that I have methods that handle loading a custom offline tile set, and place markers on the map, etc...)
public void onCreate(final Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
this.mapView = (MapView)findViewById(R.id.mapview);
...
this.intializeMapTiles();
this.mapView.setBuiltInZoomControls(true);
this.mapView.setMultiTouchControls(true);
this.mapController.setCenter(new GeoPoint((int)(50.349622 * 1E6), (int)(-71.823700 *1E6)));
...
this.mapView.invalidate();
}
Here's my activity_main.xml in this case:
<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"
tools:context=".MainActivity" >
<org.osmdroid.views.MapView
xmlns:android="http://schemas.android.com/apk/res/android"
android:id="#+id/mapview"
android:layout_width="fill_parent"
android:layout_height="fill_parent"
android:clickable="false"/>
</RelativeLayout>
When trying to get the MapView from the ContentView (acitivty_main), none of my method changes to it make any affect. It's as if I don't have access the exact MapView that's being rendered. I've tried invalidating my MapView but it doesn't matter. I get a default looking and behaving MapView.
The reason I'm trying to solve this is naturally I'm looking for my app to include more than a single MapView. I would like to include a SlidingDrawer, or some method of displaying a View with buttons that is only displayed when you long press on a map marker. (note that I have Toast pop ups being displayed on map marker long presses, so I'm good in this regard)
I'd have to add these other Views (SlideingDrawer, etc...) programmatically and not from the main_activity.xml. Even that has a catch-22, where the SlidingDrawer constructor needs an AttributeSet from xml that's painful to build yourself. (I tried) Then you also have to worry about the layout as well.
Anyone have any suggestions? General or otherwise? Thanks!
This might actually be useful to others for a number of reasons.
If you're stuck having to use a View that can only be configured the way to need via its programmatic constructor (e.g. you could just include the View from your activity_main.xml, but the View isn't what you need it to be unless you construct it yourself, like with offline tile maps using OSMDroid's MapView) then you're stuck extending that View and implementing that View's constructor that includes the AttributeSet. The AttributeSet is basically a structure parsed from the activity_main.xml xml for that view. That constructor will be called automatically in Activity when you this.setContentView(R.layout.activity_main) from onCreate(). So any custom constructor stuff needs to go in that constructor for your extended View.
For example, I had to extend the OSMDroid MapView, then implement my offline map tile source from the super entirely. NOTE you have to super() the 1st line in an extended constructor because object methods aren't available until after the inherited constructor is complete, so any super() method calls have to be to static methods.
public class FieldMapView extends MapView {
public FieldMapView(Context context, AttributeSet attrs) throws Exception {
super(
context,
256,
new DefaultResourceProxyImpl(context),
FieldMapView.getOfflineMapProvider(context, MainActivity.mapTileArchiveFilename),
null,
attrs);
this.setUseDataConnection(false);
this.setBuiltInZoomControls(false);
this.setMultiTouchControls(true);
}
Then in my activity_main.xml I point to the extended version of the View: (e.g. FieldMapView)
<RelativeLayout xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:tools="http://schemas.android.com/tools"
android:id="#+id/rootview"
android:layout_width="match_parent"
android:layout_height="match_parent"
tools:context=".MainActivity" >
<com.test.FieldMapView
xmlns:android="http://schemas.android.com/apk/res/android"
android:id="#+id/mapview"
android:layout_width="fill_parent"
android:layout_height="fill_parent"
android:clickable="false"/>
So now I have my extended View taking care of any programmatic style requirements, how did I get a SlidingDrawer working with it? I created a SlidingDrawer with a 0dip height View as the handle. I then included a LinearLayout that contains buttons, whatever you want, etc...
<RelativeLayout xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:tools="http://schemas.android.com/tools"
android:id="#+id/rootview"
android:layout_width="match_parent"
android:layout_height="match_parent"
tools:context=".MainActivity" >
<com.test.FieldMapView
xmlns:android="http://schemas.android.com/apk/res/android"
android:id="#+id/mapview"
android:layout_width="fill_parent"
android:layout_height="fill_parent"
android:clickable="false"/>
<SlidingDrawer
android:layout_width="wrap_content"
android:id="#+id/slidingDrawerActions"
android:content="#+id/action_content"
android:padding="10dip"
android:layout_height="75dip"
android:handle="#+id/handle2"
android:layout_alignBottom="#id/mapview"
android:orientation="vertical"
android:clickable="false">
<LinearLayout
android:layout_width="wrap_content"
android:id="#+id/action_content"
android:orientation="horizontal"
android:gravity="center"
android:padding="10dip"
android:background="#FF999999"
android:layout_height="wrap_content"
android:clickable="false">
<View
android:id="#id/handle2"
android:layout_width="0dip"
android:layout_height="0dip" />
<ImageButton
android:id="#+id/chatActionButton"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_margin="2dip"
android:src="#drawable/ic_action_edit"
android:text="Chat">
</ImageButton>
</LinearLayout>
</SlidingDrawer>
</RelativeLayout>
In my MainActivity onCreate() I just find the SlidingDrawer View and assign any listeners accordingly. For opening the drawer when long pressing a Marker in the MapView (this is getting more OSMDroid specific now) I naturally have an OnItemGestureListener to open the drawer:
class NodeGestureListener implements OnItemGestureListener<NodeOverlayItem> {
#Override
public boolean onItemLongPress(int index, NodeOverlayItem node) {
if(slidingDrawerActions.isOpened() || slidingDrawerActions.isMoving()) {
return false;
}
slidingDrawerActions.animateOpen();
return false;
}
The tricky part is I wanted to close it via a click on the MapView (not by touching a close button that takes up space) so I had to assign SlidingDrawer.OnDrawerOpenListener and OnDrawerCloseListener classes. They simply flipped a boolean indicating if the drawer was open or closed. I then set a simple onClickListener for the MapView that closed the drawer if it was open based on the isActionDrawerOpen set by the SlidingDrawer listeners.
public void onCreate(final Bundle savedInstanceState) {
...
this.mapView.setOnClickListener(new MapViewClickListener());
...
this.slidingDrawerActions = (SlidingDrawer)findViewById(R.id.slidingDrawerActions);
this.slidingDrawerActions.setOnDrawerOpenListener(new SlidingDrawerOpenListener());
this.slidingDrawerActions.setOnDrawerCloseListener(new SlidingDrawerCloseListener());
...
}
...
private boolean isActionDrawerOpen = false;
class SlidingDrawerOpenListener implements SlidingDrawer.OnDrawerOpenListener {
#Override
public void onDrawerOpened() {
isActionDrawerOpen = true;
}
}
class SlidingDrawerCloseListener implements SlidingDrawer.OnDrawerCloseListener {
#Override
public void onDrawerClosed() {
isActionDrawerOpen = false;
}
}
private boolean skippedMapViewClickListener = false;
class MapViewClickListener implements OnClickListener {
public void onClick(View view) {
if(isActionDrawerOpen) {
if(skippedMapViewClickListener) {
slidingDrawerActions.animateClose();
skippedMapViewClickListener = false;
} else {
skippedMapViewClickListener = true;
}
}
}
}
Note the skippedMapViewClickListener boolean. The problem I had was that the MapView OnClickListener would be called immediately after the SlidingDrawer listener when long pressing the Marker. Meaning the long press would be considered a MapView click, plus the long press itself would open the drawer before OnClickListener was called, so OnClickListener would always see the drawer as open, and would close it. What I did was effectively skip the 1st onClick this way, so the drawer would stay open until you clicked on the MapView. Seems to work great.
I hope this helps someone. There are like 4 problems I solved with this approach.

Categories

Resources