I`ve been looking for a way to enable the MPAndroidChart to only display the value(label) of data point when clicked. But It seems that I could not find it online even in the documentation.
I used the line chart and what I want is to only display the label of the certain point when clicked.
1- Enable touch in the chart
chart.setTouchEnabled(true);
2 - Create MarkerView
public class CustomMarkerView extends MarkerView {
private TextView tvContent;
public CustomMarkerView (Context context, int layoutResource) {
super(context, layoutResource);
// this markerview only displays a textview
tvContent = (TextView) findViewById(R.id.tvContent);
}
// callbacks everytime the MarkerView is redrawn, can be used to update the
// content (user-interface)
#Override
public void refreshContent(Entry e, Highlight highlight) {
tvContent.setText("" + e.getVal()); // set the entry-value as the display text
}
#Override
public int getXOffset() {
// this will center the marker-view horizontally
return -(getWidth() / 2);
}
#Override
public int getYOffset() {
// this will cause the marker-view to be above the selected value
return -getHeight();
}
}
3 - Create the tvContent view
<?xml version="1.0" encoding="utf-8"?>
<RelativeLayout xmlns:android="http://schemas.android.com/apk/res/android"
android:layout_width="wrap_content"
android:layout_height="40dp"
android:background="#drawable/markerImage" >
<TextView
android:id="#+id/tvContent"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_centerHorizontal="true"
android:layout_marginTop="7dp"
android:layout_marginLeft="5dp"
android:layout_marginRight="5dp"
android:text=""
android:textSize="12dp"
android:textColor="#android:color/white"
android:ellipsize="end"
android:singleLine="true"
android:textAppearance="?android:attr/textAppearanceSmall" />
</RelativeLayout>
4. Set the view Marker in the chart
CustomMarkerView mv = new CustomMarkerView (Context, R.layout.custom_marker_view_layout);
chart.setMarkerView(mv);
https://github.com/PhilJay/MPAndroidChart/wiki/MarkerView
Use IMarker Interface (MarkerView has been deprecated since release 3.0.0)
1. Create a new class that implements the IMarker interface
public class YourMarkerView extends MarkerView {
private TextView tvContent;
public MyMarkerView(Context context, int layoutResource) {
super(context, layoutResource);
// find your layout components
tvContent = (TextView) findViewById(R.id.tvContent);
}
// callbacks everytime the MarkerView is redrawn, can be used to update the
// content (user-interface)
#Override
public void refreshContent(Entry e, Highlight highlight) {
tvContent.setText("" + e.getY());
// this will perform necessary layouting
super.refreshContent(e, highlight);
}
private MPPointF mOffset;
#Override
public MPPointF getOffset() {
if(mOffset == null) {
// center the marker horizontally and vertically
mOffset = new MPPointF(-(getWidth() / 2), -getHeight());
}
return mOffset;
}}
2. set your marker to the chart
IMarker marker = new YourMarkerView();
chart.setMarker(marker);
Reference: https://github.com/PhilJay/MPAndroidChart/wiki/IMarker-Interface
Example in Kotlin for future you:
Define marker layout i.e. gold_marker_layout
<?xml version="1.0" encoding="utf-8"?>
<androidx.constraintlayout.widget.ConstraintLayout xmlns:android="http://schemas.android.com/apk/res/android"
android:layout_width="match_parent"
android:layout_height="match_parent"
xmlns:app="http://schemas.android.com/apk/res-auto">
<TextView
android:id="#+id/tvContent"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_centerHorizontal="true"
android:text="6828.1465"
android:textSize="12sp"
android:textColor="#android:color/white"
android:ellipsize="end"
android:singleLine="true"
android:textAppearance="?android:attr/textAppearanceSmall"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintTop_toTopOf="parent"
app:layout_constraintBottom_toBottomOf="parent" />
</androidx.constraintlayout.widget.ConstraintLayout>
Quick note: I set the initial text to 6828.1465 because that's my max-width text - watch out here.
If you don't need getXOffset and getYOffset you can assign marker like that:
lineChart.marker = object : MarkerView(context, R.layout.gold_marker_layout) {
override fun refreshContent(e: Entry, highlight: Highlight) {
(findViewById<View>(R.id.tvContent) as TextView).text = "${e.y}"
}
}
That's it, you should be good to go
Related
I have a login form validation mechanism. There are two TextInputEditTexts for email and password, both being attached to respective onFocusChangeListeners, DataBinding is used to set those, but DataBinding returns wrong listeners.
Here is the form:
<?xml version="1.0" encoding="utf-8"?>
<layout>
<data>
<variable
name="loginViewModel"
type="com.example.increaseyouriq.ui.forms.LoginFormViewModel" />
</data>
<LinearLayout
android:id="#+id/ll_login"
xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:app="http://schemas.android.com/apk/res-auto"
style="#style/parent.contentLayout"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:orientation="vertical"
android:padding="26dp">
<com.google.android.material.card.MaterialCardView
style="#style/cardOutline"
android:layout_width="match_parent"
android:layout_height="wrap_content">
<LinearLayout
android:layout_width="match_parent"
android:layout_height="match_parent"
android:orientation="vertical"
android:padding="16dp">
<TextView
style="#style/viewParent.headerText"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:text="#string/login_title" />
<LinearLayout
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_marginTop="10dp"
android:orientation="vertical">
<com.google.android.material.textfield.TextInputLayout
style="#style/textInputStyle"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:hint="#string/enter_email"
error="#{loginViewModel.loginForm.emailError}"
app:errorEnabled="#{loginViewModel.loginForm.isEmailError()}"
app:endIconMode="clear_text">
<com.google.android.material.textfield.TextInputEditText
android:id="#+id/et_login_email"
style="#style/textInputEditext"
android:layout_width="match_parent"
android:layout_height="wrap_content"
onFocus="#{loginViewModel.onFocusEmailChangeListener}"
android:text="#={loginViewModel.loginFields.email}"
android:inputType="textNoSuggestions|textEmailAddress" />
</com.google.android.material.textfield.TextInputLayout>
<com.google.android.material.textfield.TextInputLayout
style="#style/textInputStyle"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:hint="#string/enter_password"
error="#{loginViewModel.loginForm.passwordError}"
app:errorEnabled="#{loginViewModel.loginForm.isPasswordError()}"
app:endIconMode="password_toggle">
<com.google.android.material.textfield.TextInputEditText
style="#style/textInputEditext"
android:id="#+id/et_login_password"
onFocus="#{loginViewModel.onFocusPasswordChangeListener}"
android:text="#={loginViewModel.loginFields.password}"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:inputType="textPassword" />
</com.google.android.material.textfield.TextInputLayout>
</LinearLayout>
</LinearLayout>
</com.google.android.material.card.MaterialCardView>
</LinearLayout>
</layout>
Here is my LoginFormViewModel.java:
public class LoginFormViewModel extends ViewModel {
private static final String LOG_TAG = "LoginFormViewModel";
private View.OnFocusChangeListener onFocusEmail;
private View.OnFocusChangeListener onFocusPassword;
public void init() {
loginForm = new LoginForm();
onFocusPassword = (v, hasFocus) -> {
TextInputEditText et = (TextInputEditText) v;
MyUtilsApp.showLog(LOG_TAG,
String.format("%s password listener attached to :%d", String.valueOf(et.getText()), et.getId()));
if (et.getText().length() > 0 && !hasFocus) {
loginForm.isPasswordValid(true);
}
};
onFocusEmail = (v, hasFocus) -> {
TextInputEditText et = (TextInputEditText) v;
MyUtilsApp.showLog(LOG_TAG, "email listener attached");
if (et.getText().length() > 0 && !hasFocus) {
loginForm.isEmailValid(true);
}
};
}
public View.OnFocusChangeListener getOnFocusEmailChangeListener() {
return onFocusEmail;
}
public View.OnFocusChangeListener getOnFocusPasswordChangeListener() {
return onFocusPassword;
}
#BindingAdapter("onFocus")
public static void bindFocusChange(TextInputEditText editText, View.OnFocusChangeListener onFocusChangeListener) {
if (editText.getOnFocusChangeListener() == null) {
editText.setOnFocusChangeListener(onFocusChangeListener);
}
}
}
Here is my LoginActivity.java:
public class LoginActivity extends AppCompatActivity {
private static final String LOG_TAG = "LoginActivity";
ActivityLoginBinding binding;
private LoginRegisterViewModel loginRegisterViewModel;
#Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
binding = DataBindingUtil.setContentView(this,R.layout.activity_login);
setupBindings(savedInstanceState);
}
private void setupBindings(Bundle savedInstanceState) {
LoginFormViewModel loginFormViewModel = new ViewModelProvider(this).get(LoginFormViewModel.class);
if(savedInstanceState==null)
loginFormViewModel.init();
binding.setLoginViewModel(loginFormViewModel);
binding.setLifecycleOwner(this);
loginFormViewModel.getLoginFields().observe(this, loginFields -> {
Toast.makeText(LoginActivity.this,
"Email " + loginFields.getEmail() + ", Password " + loginFields.getPassword(),
Toast.LENGTH_SHORT).show();
});
binding.inLayoutLogin.btnLoginLogin.setOnClickListener(view -> {
binding.executePendingBindings();
loginFormViewModel.getLoginForm().onClick();
});
}
}
As evident, I have set both listeners to be of onFocusEmail kind, just because onFocusPassword is being attached to both TextInputEditTexts during runtime.
Here are logs:
com.example.increaseyouriq E/LoginFormViewModel: password listener attached to :2131296499
com.example.increaseyouriq E/LoginFormViewModel: ttf password listener attached to :2131296499
onFocusEmail is nowhere to be seen, it has been attached during runtime.
for more details on code see here
I did some experiments on your code and I believe I found out the underlying reason.
Problem:
Neither of your listeners are attached to your email field (TextInputEditText "et_login_email").
Reason:
The reason is that you have a null check here;
#BindingAdapter("onFocus")
public static void bindFocusChange(TextInputEditText editText, View.OnFocusChangeListener onFocusChangeListener) {
if (editText.getOnFocusChangeListener() == null) {
editText.setOnFocusChangeListener(onFocusChangeListener);
}
}
And your email field has an onFocusChangeListener already which is set by "app:endIconMode="clear_text"" attribute.
<com.google.android.material.textfield.TextInputLayout
...
android:hint="#string/enter_email"
..."
app:endIconMode="clear_text">
You cannot set a listener to the email field because its listener is not null from the begining and the current method (bindFocusChange()) only sets it when it is null.
Solution 1:
Remove your null check when setting the onFocusChangeListeners.
Solution 2:
Utilize data binding library's automatic method selection with less code.
Change your custom attribute's name "onFocus" to "onFocusChangeListener". As the TextInputEditText fields have a matching setter method (setOnFocusChangeListener) for this name, the library can handle this automatically and you can remove your "bindFocusChange(...)" method completely:
The basic view hierarchy is this:
secondActivity
linearLayout(LinearLayout)
constLayout(ConstraintLayout)
textbox(TextView)
image(ImageView)
image2
image3
...
The textbox TextView has visibility GONE, and goal is to make it VISIBLE on clicking other visible siblings, change some colors and text, and when clicked again it must be invisible again and reverse all changes.
Cant understand whatever it is that am missing. I have checked many older projects in which I did the same thing and cant find any visible differences as to why this code is not working now.
secondActivity.java
public class secondActivity extends Activity {
public boolean isTextBoxHidden;
public ConstraintLayout constLayout;
public TextView textbox;
#Override protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_second);
constLayout = findViewById(R.id.constLayout);
textbox = findViewById(R.id.textbox);
isTextBoxHidden = false;
// SETTING UP LISTENER
View.OnClickListener clickListener = new View.OnClickListener() {
#Override
public void onClick(View v) {
if(!isTextBoxHidden) {
constLayout.setBackgroundColor(Color.BLACK); //setting color on previously
v.setBackgroundColor(Color.BLACK); //setting color on visible view
textbox.setText("whatever");
textbox.setVisibility(View.VISIBLE); //was gone
isTextBoxHidden = true;
}
else {
textbox.setVisibility(View.GONE); //hide again
constLayout.setBackgroundColor(Color.WHITE);
v.setBackgroundColor(Color.WHITE);
isTextBoxHidden = false;
}
}
};
// INSERTING LISTENERS into all children
for(int i=0; i<constLayout.getChildCount(); i++) {
constLayout.getChildAt(i).setOnClickListener(clickListener);
}
}
}
activity_second.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:id="#+id/linearLayout"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:orientation="vertical"
tools:context=".secondActivity">
<android.support.constraint.ConstraintLayout
android:id="#+id/constLayout"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:background="#android:color/white">
<TextView
android:id="#+id/textbox"
android:layout_width="0dp"
android:layout_height="wrap_content"
android:gravity="center"
android:text="example"
android:visibility="gone"
app:layout_constraintLeft_toLeftOf="parent"
app:layout_constraintRight_toRightOf="parent"
app:layout_constraintTop_toTopOf="parent"/>
<ImageButton
android:id="#+id/image"
android:layout_width="0dp"
android:layout_height="wrap_content"
android:adjustViewBounds="true"
android:scaleType="fitXY"
android:src="#drawable/example"
android:clickable="true"
app:layout_constraintBottom_toBottomOf="parent"
app:layout_constraintRight_toRightOf="parent"
app:layout_constraintLeft_toLeftOf="parent"/>
<!--few more clones of the first imageButton-->
</android.support.constraint.ConstraintLayout>
</LinearLayout>
I can't see where you're setting textbox reference, so maybe that's a clue.
Edit:
Did compiled that example you have provided and everything works correctly, but i assume that [...] gone again for good meant you probably want this to be one shot action so instead of boolean just use Boolean and compare it to null.
Edit:
On the second thought you can just remove isTextBoxHidden = false; in else branch
i am currently rewriting our app from ios to android and I couln't get a proper effect to work. So in iOS we used the this library to achieve the fixed header over the content scrollview with parallax effect. I quickly modified the example and export it as a gif so it's more clear what we're trying to achieve:
So first of all I tried something with CollapsingToolbarLayout where I came across this snippet. So basically the snippet adds a AppBarLayout.Behavior what achieves the zoom effect. Overhaul it does not feel smooth.
Next I found PullZoomView which seems popular in android. The zoom and the scroll behaviour feels great. The only problem is that the header is not fixed and gets overscrolled by the listview like this:
So I dig into the code and tried to modified it for my propose but it seems hacky and complicated.
So it seems like from what I saw in all libraries that this overscroll over the header behaviour is more in common. Does someone has any advice how can I achieve the behaviour like I want before I write my own solution? Is there any library maybe which I don't found?
Doesn't directly answer your question but I made a library that has a component which expands on click/touch. I think you might be able to use the code to create your own scrollbar header thing.
https://github.com/NadavTasher/ToolLibs/blob/master/lightool/src/main/java/nadav/tasher/lightool/graphics/views/ExpandingView.java
e.g. Make the fixed view your header, and the expanding view your scrollview
I wrote a solution by my own right after I submit this question. I really liked the concept in the library from iOS that you can have individual UIViewController for the header and for the content part so in Android it seems that Fragments are the way to go here. I want to share my code if anyone want to have the same effect:
Add the following dependencies to yur build.gradle:
implementation 'me.everything:overscroll-decor-android:1.0.4'
implementation 'com.github.ksoichiro:android-observablescrollview:1.6.0'
My class (sorry it's a bit messy):
public abstract class ParallaxHeaderActivity extends AppCompatActivity implements ObservableScrollViewCallbacks {
private ObservableScrollView scrollView;
private LinearLayout headerView;
private FrameLayout contentView;
private int headerHeight = 0;
private int minimumHeaderHeight = 0;
protected void setContentView(int layout, Fragment header, Fragment content){
super.setContentView(layout);
ViewGroup rootView = (ViewGroup) ((ViewGroup) this
.findViewById(android.R.id.content)).getChildAt(0);
headerHeight = (int)convertDpToPixel(260, this);
minimumHeaderHeight = (int)convertDpToPixel(160, this);
int contentViewId = View.generateViewId();
contentView = new FrameLayout(this);
contentView.setLayoutParams(new LinearLayout.LayoutParams(ViewGroup.LayoutParams.MATCH_PARENT, ViewGroup.LayoutParams.WRAP_CONTENT));
contentView.setPadding(0, headerHeight, 0, 0);
contentView.setId(contentViewId);
scrollView = new ObservableScrollView(this);
scrollView.setLayoutParams(new LinearLayout.LayoutParams(ViewGroup.LayoutParams.MATCH_PARENT, ViewGroup.LayoutParams.MATCH_PARENT));
scrollView.setScrollViewCallbacks(this);
scrollView.setFillViewport(true);
scrollView.addView(contentView);
scrollView.setScrollViewCallbacks(this);
IOverScrollDecor decor = new VerticalOverScrollBounceEffectDecorator(new ScrollViewOverScrollDecorAdapter(scrollView));
decor.setOverScrollUpdateListener(new IOverScrollUpdateListener() {
#Override
public void onOverScrollUpdate(IOverScrollDecor decor, int state, float offset) {
if (offset > 0) {
// 'view' is currently being over-scrolled from the top.
update((int)-offset);
}
}
});
rootView.addView(scrollView);
addFragment(contentViewId, content);
int headerViewId = View.generateViewId();
headerView = new LinearLayout(this);
headerView.setLayoutParams(new LinearLayout.LayoutParams(ViewGroup.LayoutParams.MATCH_PARENT, headerHeight));
headerView.setId(headerViewId);
rootView.addView(headerView);
addFragment(headerViewId, header);
}
#Override
public void onScrollChanged(int scrollY, boolean firstScroll, boolean dragging) {
update(scrollY);
}
#Override
public void onDownMotionEvent() {
}
#Override
public void onUpOrCancelMotionEvent(ScrollState scrollState) {
}
private void addFragment(int id, Fragment fragment){
final FragmentTransaction transaction = getSupportFragmentManager().beginTransaction();
transaction.replace(id, fragment);
transaction.addToBackStack(null);
transaction.commit();
}
private void update(final int scrollY) {
headerView.getLayoutParams().height = Math.max(headerHeight - scrollY, minimumHeaderHeight);
headerView.requestLayout();
}
public static float convertDpToPixel(float dp, Context context){
return dp * ((float) context.getResources().getDisplayMetrics().densityDpi / DisplayMetrics.DENSITY_DEFAULT);
}
}
In your Activity you can call it like:
setContentView(R.layout.activity_main, new HeaderFragment(), new ContentFragment());
fragment_content.xml:
<?xml version="1.0" encoding="utf-8"?>
<FrameLayout 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="wrap_content"
tools:context=".ContentFragment">
<TextView
android:layout_width="match_parent"
android:layout_height="match_parent"
android:background="#android:color/white"
android:paddingLeft="#dimen/activity_horizontal_margin"
android:paddingTop="#dimen/activity_vertical_margin"
android:paddingRight="#dimen/activity_horizontal_margin"
android:paddingBottom="#dimen/activity_vertical_margin"
android:text="#string/lipsum"/>
</FrameLayout>
fragment_header.xml:
<?xml version="1.0" encoding="utf-8"?>
<android.support.constraint.ConstraintLayout xmlns:app="http://schemas.android.com/apk/res-auto"
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">
<ImageView
android:layout_width="0dp"
android:layout_height="0dp"
android:scaleType="centerCrop"
android:src="#drawable/example"
app:layout_constraintBottom_toBottomOf="parent"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toTopOf="parent" />
<TextView
android:text="Test"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_marginStart="8dp"
android:layout_marginTop="8dp"
android:layout_marginEnd="8dp"
android:layout_marginBottom="8dp"
android:ellipsize="end"
android:gravity="center_vertical"
android:maxLines="1"
android:minHeight="?attr/actionBarSize"
android:textColor="#android:color/white"
android:textSize="20sp"
app:layout_constraintBottom_toBottomOf="parent"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toTopOf="parent" />
</android.support.constraint.ConstraintLayout>
Context
I am writing an app for Android, and I use OSMBonusPack to easily display markers.
I display a marker to show the user's position, got with the phone GPS, and I add a FolderOverlay to group other markers (these indicate POIs from Wikipedia, retrieved with a custom API).
I included the bonuspack_bubble.xml layout file in my projet, because I wanted to modify it to scale the "More info" button properly.
Problem
The problem is that the POI markers are correctly added on the map (they show the icon, and when tapped, the Info bubble shows, filled with the title and description), but the first marker indicating the user's position displays an empty Info bubble.
I have tried to remove the FolderOverlay, I tried to add the starterMarker to the same overlay as the POIs, I tried to only display the starterMarker... Nothing works.
What am I doing wrong? If you need clarification on some points or code, feel free to ask!
Thanks!
Note: using osmbonuspack v5.3 (AAR), osmdroid 4.3, Android Studio 1.2.2, and testing on a Galaxy S4 with Android 4.2.2
Code:
Function in main Fragment
public void drawMap(Location location, MapView map, LocationManager locationManager, LocationListener locationListener) {
...
final GeoPoint startPoint = new GeoPoint(location.getLatitude(), location.getLongitude());
// Now we add a marker using osmBonusPack
Marker startMarker = new Marker(map);
startMarker.setPosition(startPoint);
startMarker.setAnchor(Marker.ANCHOR_CENTER, Marker.ANCHOR_BOTTOM);
map.getOverlays().add(startMarker);
// We can change some properties of the marker (don't forget to refresh the map !!)
startMarker.setInfoWindow(new CustomInfoWindow(map));
startMarker.setIcon(ContextCompat.getDrawable(getActivity(), R.drawable.ic_place));
startMarker.setTitle(getString(R.string.you_are_here));
map.invalidate();
...
// We create an Overlay Folder to store every POI, so that they are grouped in clusters
// if there are too many of them
RadiusMarkerClusterer poiMarkers = new RadiusMarkerClusterer(getActivity());
Drawable clusterIconD = ContextCompat.getDrawable(getActivity(), R.drawable.marker_cluster);
Bitmap clusterIcon = ((BitmapDrawable)clusterIconD).getBitmap();
poiMarkers.setIcon(clusterIcon);
map.getOverlays().add(poiMarkers);
// poiList is an ArrayList of custom POIs
for (POI poi:poiList) {
double mLat = poi.getLatitude();
double mLong = poi.getLongitude();
GeoPoint poiWaypoint = new GeoPoint(mLat, mLong);
Marker marker = new Marker(map);
marker.setPosition(poiWaypoint);
marker.setAnchor(Marker.ANCHOR_CENTER, Marker.ANCHOR_BOTTOM);
marker.setRelatedObject(poi);
marker.setInfoWindow(new CustomInfoWindow(map));
marker.setTitle(poi.getName());
marker.setSnippet(poi.getSitelink());
Drawable icon = ContextCompat.getDrawable(getActivity(), R.drawable.ic_place);
marker.setIcon(icon);
poiMarkers.add(marker);
}
map.invalidate();
}
CustomInfoWindow.java
public class CustomInfoWindow extends MarkerInfoWindow {
private POI mSelectedPoi;
public CustomInfoWindow(MapView mapView) {
super(R.layout.bonuspack_bubble, mapView);
Button btn = (Button) (mView.findViewById(R.id.bubble_moreinfo));
btn.setOnClickListener(new View.OnClickListener() {
public void onClick(View view) {
if (mSelectedPoi.getSitelink() != null) {
Intent myIntent = new Intent(Intent.ACTION_VIEW, Uri.parse(mSelectedPoi.getSitelink()));
view.getContext().startActivity(myIntent);
}
}
});
}
#Override
public void onOpen(Object item){
super.onOpen(item);
mView.findViewById(R.id.bubble_moreinfo).setVisibility(View.VISIBLE);
Marker marker = (Marker)item;
mSelectedPoi = (POI)marker.getRelatedObject();
}
}
POI.java
public class POI {
// We define every variable returned by the WikiJourney API
private double latitude;
private double longitude;
private String name;
private String sitelink;
private String type_name;
private int type_id;
private int id;
public POI(...) { }
}
_bonuspack_bubble.xml_
<?xml version="1.0" encoding="UTF-8"?>
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:orientation="horizontal"
android:background="#drawable/bonuspack_bubble" >
<ImageView android:id="#+id/bubble_image"
android:layout_width="65dp"
android:layout_height="65dp"
android:visibility="gone" />
<LinearLayout
android:layout_width="match_parent"
android:layout_height="match_parent"
android:paddingStart="5dp"
android:paddingEnd="5dp"
android:paddingLeft="5dp"
android:paddingRight="5dp"
android:orientation="vertical" >
<LinearLayout
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:orientation="horizontal" >
<TextView android:id="#+id/bubble_title"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:textColor="#000000"
android:maxEms="17"
android:layout_gravity="start"
android:layout_weight="1"
android:text="Title" />
<Button android:id="#+id/bubble_moreinfo"
android:background="#drawable/btn_moreinfo"
android:visibility="gone"
android:layout_width="25sp"
android:layout_height="25sp"
android:layout_gravity="end"
android:layout_weight="0" />
</LinearLayout>
<TextView android:id="#+id/bubble_description"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:textColor="#000000"
android:textSize="12sp"
android:maxEms="17"
android:text="Description" />
<TextView android:id="#+id/bubble_subdescription"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:textColor="#000000"
android:textSize="10sp"
android:maxEms="17"
android:text="Address"
android:visibility="gone" />
</LinearLayout>
</LinearLayout>
What is really surprising is that your app doesn't crash:
You set your CustomInfoWindow to the startMarker, without setting any related POI object.
But in CustomInfoWindow.onOpen, you get the related object, and use it without testing if it's not null - and if it's really a POI.
This can not work.
Either define a "StartInfoWindow" class, or ensure your CustomInfoWindow fully supports your startMarker.
A hint: share the same CustomInfoWindow object between all your POI markers. It is faster, and better for memory.
I've wrote a small application, which shows several android cards. But I'd like to be able to set a colour and title to the top of the card like in the image below, so far I haven't found any information online how to do this. So some help would be fantastic :-)
(My code so far does not accomplish the above, so far my code just produces regular all white cardviews)
My code so far is below:
CardAdapter.java
public class CardAdapter extends RecyclerView.Adapter<CardAdapter.ViewHolder> {
public List<TTItem> posts = new ArrayList<>();
public void addItems(List<TTItem> items) {
posts.addAll(items);
notifyDataSetChanged();
}
public void clear() {
posts.clear();
notifyDataSetChanged();
}
#Override
public ViewHolder onCreateViewHolder(ViewGroup parent, int viewType) {
Context context = parent.getContext();
View view = View.inflate(context, R.layout.item_cardview, null);
return new ViewHolder(view);
}
#Override
public void onBindViewHolder(ViewHolder holder, int position) {
holder.mTextView.setText(posts.get(position).title);
Picasso.with(holder.mImageView.getContext()).load(posts.get(position).images[0]).into(holder.mImageView);
}
#Override
public int getItemCount() {
return posts.size();
}
static class ViewHolder extends RecyclerView.ViewHolder {
public TextView mTextView;
public ImageView mImageView;
public ViewHolder(View view) {
super(view);
mTextView = (TextView) view.findViewById(R.id.textview);
mImageView = (ImageView) view.findViewById(R.id.imageView);
}
}
}
MainActivity.java
public class MainActivity extends ActionBarActivity implements SwipeRefreshLayout.OnRefreshListener {
#InjectView(R.id.mainView)
RecyclerView mRecyclerView;
#InjectView(R.id.refreshContainer)
SwipeRefreshLayout refreshLayout;
private LinearLayoutManager mLayoutManager;
private CardAdapter mAdapter;
#Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
ButterKnife.inject(this);
mRecyclerView.setHasFixedSize(true);
refreshLayout.setOnRefreshListener(this);
TypedValue tv = new TypedValue();
int actionBarHeight = 0;
if (getTheme().resolveAttribute(R.attr.actionBarSize, tv, true)) {
actionBarHeight = TypedValue.complexToDimensionPixelSize(tv.data, getResources().getDisplayMetrics());
}
refreshLayout.setProgressViewEndTarget(true, actionBarHeight);
mAdapter = new CardAdapter();
mRecyclerView.setAdapter(mAdapter);
// use a linear layout manager
mLayoutManager = new GridLayoutManager(this, 1);
mRecyclerView.setLayoutManager(mLayoutManager);
}
#Override
public boolean onCreateOptionsMenu(Menu menu) {
// Inflate the menu; this adds items to the action bar if it is present.
return true;
}
#Override
public boolean onOptionsItemSelected(MenuItem item) {
// Handle action bar item clicks here. The action bar will
// automatically handle clicks on the Home/Up button, so long
// as you specify a parent activity in AndroidManifest.xml.
return super.onOptionsItemSelected(item);
}
#Override
public void onRefresh() {
mAdapter.clear();
Handler handler = new Handler();
handler.postDelayed(new Runnable() {
public void run() {
refreshLayout.setRefreshing(false);
}
}, 2500);
}
}
Activity_main.xml
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:tools="http://schemas.android.com/tools"
android:layout_width="match_parent"
xmlns:app="http://schemas.android.com/apk/res-auto"
android:layout_height="match_parent"
android:orientation="vertical"
tools:context=".MainActivity">
<android.support.v4.widget.SwipeRefreshLayout
android:id="#+id/refreshContainer"
android:layout_width="match_parent"
android:layout_height="match_parent">
<android.support.v7.widget.RecyclerView
android:id="#+id/mainView"
android:scrollbars="vertical"
android:layout_width="match_parent"
android:layout_height="match_parent" />
</android.support.v4.widget.SwipeRefreshLayout>
</LinearLayout>
item_cardview.xml:
<?xml version="1.0" encoding="utf-8"?>
<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="200dp"
android:layout_height="200dp"
android:padding="10dp"
card_view:cardCornerRadius="2dp"
card_view:contentPadding= "5dp"
card_view:cardUseCompatPadding="true"
>
<LinearLayout
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:orientation="vertical">
<ImageView
android:id="#+id/imageView"
android:layout_width="match_parent"
android:layout_height="150dp"
android:scaleType="centerCrop"/>
<TextView
android:id="#+id/textview"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:textSize="20sp"
android:textStyle="bold"
android:layout_gravity="center"/>
</LinearLayout>
</android.support.v7.widget.CardView>
The only change I've made from yours is, card_view:cardCornerRadius="8dp"> and removed the imageview(as no longer needed)
Screenshot of flashcard not filling to half of card:
I believe this is (almost) exact layout of what you want. It is pretty self-explanatory but feel free to ask if something's not clear.
<?xml version="1.0" encoding="utf-8"?>
<android.support.v7.widget.CardView xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:app="http://schemas.android.com/apk/res-auto"
xmlns:card_view="http://schemas.android.com/apk/res-auto"
android:id="#+id/card_view"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_gravity="center"
app:cardElevation="8dp"
card_view:cardCornerRadius="2dp">
<LinearLayout
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:orientation="vertical">
<LinearLayout <!-- This is the specific part you asked to color -->
android:id="#+id/heading_layout"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:background="#color/teal_500"
android:padding="36dp"
android:orientation="vertical">
<TextView
android:id="#+id/tv_heading"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="22 mins to Ancona"
android:textColor="#color/white"
android:textStyle="bold"
android:textSize="36sp" />
<TextView
android:id="#+id/tv_subheading"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_marginTop="24dp"
android:layout_below="#+id/tv_heading"
android:text="Light traffic on ss16"
android:textColor="#color/teal_200"
android:textSize="24sp" />
</LinearLayout>
<ImageView
android:id="#+id/iv_map"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:contentDescription="Assigned delivery boy"
android:scaleType="fitXY"
android:src="#drawable/bg_map" />
<TextView
android:id="#+id/tv_footer"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_margin="16dp"
android:layout_below="#+id/tv_heading"
android:text="It is just an example!"
android:textColor="#color/grey_500"
android:textStyle="bold"
android:textSize="24sp" />
</LinearLayout>
</android.support.v7.widget.CardView>
I have replaced your mapFragment (presumably) with imageView to reduce complications.
Update: As the question now addresses the infamous "round corner" problem, this is actually by design. Yes, it is a big flaw. But the solution (as given in docs Here) would be to use card_view:cardPreventCornerOverlap="false" attribute (which I don't think does anything good because it just makes card square again).
See these questions for a good reference to this problem:
Appcompat CardView and Picasso no rounded Corners
Make ImageView fit width of CardView
From my understanding, you can change the colour of a CardView in it's entirety but not parts of it. I'm not sure how that would even work.
What you can do, is nest a TextView with the title within the CardView, then colour the background of the TextView to the colour you would like. Use appropriate margins/padding for a uniform look. Add a background to your TextView and see what you get.
Your TextView is already using the match_parent parameter on your android:layout_width="" so you would only need to add the background like so:
<TextView
android:id="#+id/textview"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:textSize="20sp"
android:textStyle="bold"
android:layout_gravity="center"
android:background="#FF4444"/>
To change the entire CardView colour like I mentioned at the beginning you can do it the same way or programmatically like so:
cardView.setCardBackgroundColor(COLOURHERE);