I'm trying to create a Spinner like custom view with list of values. I've managed to get it started with the following code below.
public class SelectionTextView extends TextInputEditText implements View.OnClickListener {
private CharSequence[] entries, values;
private CharSequence value;
#Override
public void onClick(View v) {
new AlertDialog.Builder(v.getContext())
.setTitle("Title")
.setItems(entries, new DialogInterface.OnClickListener() {
#Override
public void onClick(DialogInterface dialog, int which) {
value = values[which];
SelectionTextView.super.setText(entries[which]);
}
})
.create()
.show();
}
public SelectionTextView(final Context context, #Nullable AttributeSet attrs) {
super(context, attrs);
super.setCursorVisible(false);
super.setFocusable(false);
super.setFocusableInTouchMode(false);
super.setInputType(InputType.TYPE_NULL);
super.setOnClickListener(this);
}
public void setEntries(CharSequence[] entries) {
this.entries = entries;
super.setOnClickListener(this);
}
public void setValues(CharSequence[] values) {
this.values = values;
}
public void setValue(Object value) {
this.value = value.toString();
}
public CharSequence getValue() {
return value;
}
}
However, I would like to implement something like onValueChanged, onEntryChanged. How would I go about doing this? Also how can I make the value attribute bindable through Android Data Binding.
Appreciate any help.
UPDATE: 03/13/2018
Posting my complete and working SelectionTextView.class.
package com.mycompany.myproject;
import android.content.Context;
import android.content.DialogInterface;
import android.content.res.TypedArray;
import android.support.annotation.Nullable;
import android.support.design.widget.TextInputEditText;
import android.support.v7.app.AlertDialog;
import android.text.InputType;
import android.util.AttributeSet;
import android.view.View;
public class SelectionTextView extends TextInputEditText implements View.OnClickListener {
private CharSequence[] entries, values;
private String value;
private String prompt;
private OnValueChangedListener listener;
#Override
public void onClick(View v) {
new AlertDialog.Builder(v.getContext())
.setTitle(prompt)
.setItems(entries, new DialogInterface.OnClickListener() {
#Override
public void onClick(DialogInterface dialog, int which) {
setValue(values[which].toString());
}
})
.create()
.show();
}
public SelectionTextView(final Context context, #Nullable AttributeSet attrs) {
super(context, attrs);
super.setCursorVisible(false);
super.setFocusable(false);
super.setFocusableInTouchMode(false);
super.setInputType(InputType.TYPE_NULL);
super.setOnClickListener(this);
TypedArray typedArray = context.getTheme().obtainStyledAttributes(attrs, R.styleable.SelectionTextView, 0, 0);
try {
entries = typedArray.getTextArray(R.styleable.SelectionTextView_entries);
values = typedArray.getTextArray(R.styleable.SelectionTextView_values);
value = typedArray.getString(R.styleable.SelectionTextView_value);
prompt = typedArray.getString(R.styleable.SelectionTextView_prompt);
} finally {
typedArray.recycle();
}
}
public void setOnValueChangeListener(OnValueChangedListener listener) {
setValue(this.value);
}
public void setEntries(CharSequence[] entries) {
this.entries = entries;
invalidate();
}
public void setValues(CharSequence[] values) {
this.values = values;
invalidate();
}
public void setValue(String value) {
this.value = value;
if (value != null) {
if (entries != null && values != null) {
for (int i = 0; i < entries.length; i++) {
if (values[i].toString().equals(value)) {
super.setText(entries[i].toString());
invalidate();
break;
}
}
}
}
}
public void setValue(Integer value) {
if (value != null) {
setValue(value.toString());
}
}
public String getValue() {
return value;
}
public interface OnValueChangedListener {
void onValueChange(SelectionTextView view, String value);
}
}
Then in my actual project, I just create a class with all the necessary binding.
package com.mycompany.myproject;
import android.databinding.BaseObservable;
import android.databinding.BindingAdapter;
import android.databinding.InverseBindingAdapter;
import android.databinding.InverseBindingListener;
import android.databinding.InverseBindingMethod;
import android.databinding.InverseBindingMethods;
import android.widget.TextView;
import com.mycompany.views.SelectionTextView;
#InverseBindingMethods({
#InverseBindingMethod(type = SelectionTextView.class, attribute = "value"),
})
public class BindingManager extends BaseObservable {
#BindingAdapter("android:text")
public static void setText(TextView view, Integer value) {
if (value != null) {
view.setText(Integer.toString(value));
}
}
#InverseBindingAdapter(attribute = "android:text")
public static Integer getText(TextView view) {
if (view.getText().length() == 0)
return null;
else
return Integer.parseInt(view.getText().toString());
}
#BindingAdapter(value = {"onValueChange", "valueAttrChanged"}, requireAll = false)
public static void setValueChangedListener(SelectionTextView view,
final SelectionTextView.OnValueChangedListener listener,
final InverseBindingListener valueChange) {
if (valueChange == null) {
view.setOnValueChangeListener(listener);
} else {
view.setOnValueChangeListener(new SelectionTextView.OnValueChangedListener() {
#Override
public void onValueChange(SelectionTextView view, String value) {
if (listener != null) {
listener.onValueChange(view, value);
}
valueChange.onChange();
}
});
}
}
#InverseBindingAdapter(attribute = "value")
public static Integer getValue(SelectionTextView view) {
if (view.getValue() == null) {
return null;
} else {
return Integer.parseInt(view.getValue());
}
}
#BindingAdapter("value")
public static void setValue(SelectionTextView view, Integer value) {
if (value != null) {
view.setValue(value);
} else {
view.setValue("");
}
}
#BindingAdapter("value")
public static void setValue(SelectionTextView view, String value) {
if (value != null) {
view.setValue(value);
} else {
view.setValue("");
}
}
}
In your AttributeSet constructor, add this:
TypedArray a = context.getTheme().obtainStyledAttributes(
attrs,
R.styleable.SelectionTextView, 0, 0);
value = a.getString(R.styleable.SelectionTextView_value, "");
In your res\values folder, add an attrs.xml that contains:
<resources>
<declare-styleable name="SelectionTextView">
<attr name="value" format="string" />
</declare-styleable>
</resources>
For onValueChanged, just define an interface that has the onValueChanged method and a "register" method with the interface as parameter and store that in a data member. Then, in the setValue method, call that interface.
Related
Currently working on a ExpandableListView in android using RecyclerView. I have done almost all the thing but somehow I am getting a NullpointerException which I can not sort out.Any help will be appreciated.
I am sharing the code snippet and also the git link
Used Library
Code I have tried
The app is crasing at this line in the apadter class
public DriverScheduleExpandableAdapter(Context mContext, #NonNull
List<DriverSchedule.Schedules> parentList) {
super(parentList);//////**this is where the app is crashing**
mRecipeList = parentList;
mInflater = LayoutInflater.from(mContext);
}
Error coming is :
Caused by: java.lang.NullPointerException: Attempt to invoke interface method 'java.util.Iterator java.util.List.iterator()' on a null object reference
at com.bignerdranch.expandablerecyclerview.model.ExpandableWrapper.generateChildItemList(ExpandableWrapper.java:99)
at com.bignerdranch.expandablerecyclerview.model.ExpandableWrapper.<init>(ExpandableWrapper.java:33)
at com.bignerdranch.expandablerecyclerview.ExpandableRecyclerAdapter.generateParentWrapper(ExpandableRecyclerAdapter.java:1357)
at com.bignerdranch.expandablerecyclerview.ExpandableRecyclerAdapter.generateFlattenedParentChildList(ExpandableRecyclerAdapter.java:1326)
at com.bignerdranch.expandablerecyclerview.ExpandableRecyclerAdapter.<init>(ExpandableRecyclerAdapter.java:120)
at com.rtstl.expandablelistview.adapter.DriverScheduleExpandableAdapter.<init>(DriverScheduleExpandableAdapter.java:23)
at com.rtstl.expandablelistview.MainActivity.inflateadapter(MainActivity.java:50)
at com.rtstl.expandablelistview.MainActivity.initview(MainActivity.java:41)
at com.rtstl.expandablelistview.MainActivity.onCreate(MainActivity.java:36)
at android.app.Activity.performCreate(Activity.java:6357)
at android.app.Instrumentation.callActivityOnCreate(Instrumentation.java:1108)
at android.app.ActivityThread.performLaunchActivity(ActivityThread.java:2408)
at android.app.ActivityThread.handleLaunchActivity(ActivityThread.java:2515)
at android.app.ActivityThread.access$1000(ActivityThread.java:154)
at android.app.ActivityThread$H.handleMessage(ActivityThread.java:1379)
at android.os.Handler.dispatchMessage(Handler.java:102)
at android.os.Looper.loop(Looper.java:157)
at android.app.ActivityThread.main(ActivityThread.java:5571)
at java.lang.reflect.Method.invoke(Native Method)
at com.android.internal.os.ZygoteInit$MethodAndArgsCaller.run(ZygoteInit.java:745)
at com.android.internal.os.ZygoteInit.main(ZygoteInit.java:635)
MainActivity.java
package com.rtstl.expandablelistview;
import android.content.Context;
import android.databinding.DataBindingUtil;
import android.os.Bundle;
import android.support.v7.app.AppCompatActivity;
import android.support.v7.widget.LinearLayoutManager;
import android.widget.Toast;
import com.google.gson.Gson;
import com.google.gson.reflect.TypeToken;
import com.rtstl.expandablelistview.adapter.DriverScheduleAdapter;
import com.rtstl.expandablelistview.adapter.DriverScheduleExpandableAdapter;
import com.rtstl.expandablelistview.databinding.ActivityMainBinding;
import com.rtstl.expandablelistview.model.DriverSchedule;
import java.io.ByteArrayOutputStream;
import java.io.IOException;
import java.io.InputStream;
public class MainActivity extends AppCompatActivity {
ActivityMainBinding binding;
Context mContext;
DriverSchedule list_driver;
DriverScheduleAdapter adapter;
DriverScheduleExpandableAdapter adapterExp;
Gson gson;
#Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
mContext=this;
gson=new Gson();
initview();
}
private void initview() {
binding= DataBindingUtil.setContentView(this, R.layout.activity_main);
inflateadapter();
}
private void inflateadapter() {
////for reading file from raw folder otherwise it's not required
list_driver= gson.fromJson(readFileFromRawDirectory(R.raw.driverschedule), new TypeToken<DriverSchedule>(){}.getType());
////////////////////////////////////////
Toast.makeText(mContext,""+list_driver.getData().getSclist().size(),Toast.LENGTH_SHORT).show();
adapterExp = new DriverScheduleExpandableAdapter(mContext, list_driver.getData().getSclist());
binding.rvRecycle.setLayoutManager(new LinearLayoutManager(this));
binding.rvRecycle.setAdapter(adapter);
}
private String readFileFromRawDirectory(int resourceId){
InputStream iStream = this.getResources().openRawResource(resourceId);
ByteArrayOutputStream byteStream = null;
try {
byte[] buffer = new byte[iStream.available()];
iStream.read(buffer);
byteStream = new ByteArrayOutputStream();
byteStream.write(buffer);
byteStream.close();
iStream.close();
//inflateadapter();
} catch (IOException e) {
e.printStackTrace();
}
return byteStream.toString();
}
}
activity_main.xml
<?xml version="1.0" encoding="utf-8"?>
<layout xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:tools="http://schemas.android.com/tools">
<RelativeLayout
android:layout_width="match_parent"
android:layout_height="match_parent"
tools:context=".MainActivity">
<android.support.v7.widget.RecyclerView
android:id="#+id/rv_Recycle"
android:layout_width="match_parent"
android:layout_height="match_parent" />
</RelativeLayout>
</layout>
Adapter class
package com.rtstl.expandablelistview.adapter;
import android.content.Context;
import android.support.annotation.NonNull;
import android.view.LayoutInflater;
import android.view.View;
import android.view.ViewGroup;
import com.bignerdranch.expandablerecyclerview.ExpandableRecyclerAdapter;
import com.rtstl.expandablelistview.R;
import com.rtstl.expandablelistview.model.DriverSchedule;
import java.util.List;
public class DriverScheduleExpandableAdapter extends ExpandableRecyclerAdapter<DriverSchedule.Schedules, DriverSchedule.Alloted_kids, RouteViewHolder, KidViewHolder> {
private LayoutInflater mInflater;
private List<DriverSchedule.Schedules> mRecipeList;
private static final int PARENT_NORMAL = 1;
private static final int CHILD_NORMAL = 3;
public DriverScheduleExpandableAdapter(Context mContext, #NonNull List<DriverSchedule.Schedules> parentList) {
super(parentList);//////**this is where the app is crashing**
mRecipeList = parentList;
mInflater = LayoutInflater.from(mContext);
}
#NonNull
#Override
public RouteViewHolder onCreateParentViewHolder(#NonNull ViewGroup parentViewGroup, int viewType) {
View recipeView;
switch (viewType) {
default:
case PARENT_NORMAL:
recipeView = mInflater.inflate(R.layout.group_item, parentViewGroup, false);
break;
}
return new RouteViewHolder(recipeView);
}
#NonNull
#Override
public KidViewHolder onCreateChildViewHolder(#NonNull ViewGroup childViewGroup, int viewType) {
View ingredientView;
switch (viewType) {
default:
case CHILD_NORMAL:
ingredientView = mInflater.inflate(R.layout.child_item, childViewGroup, false);
break;
}
return new KidViewHolder(ingredientView);
}
#Override
public void onBindParentViewHolder(#NonNull RouteViewHolder parentViewHolder, int parentPosition, #NonNull DriverSchedule.Schedules parent) {
parentViewHolder.bind(parent);
}
#Override
public void onBindChildViewHolder(#NonNull KidViewHolder childViewHolder, int parentPosition, int childPosition, #NonNull DriverSchedule.Alloted_kids child) {
childViewHolder.bind(child);
}
#Override
public int getParentViewType(int parentPosition) {
return PARENT_NORMAL;
}
#Override
public int getChildViewType(int parentPosition, int childPosition) {
return CHILD_NORMAL;
}
#Override
public boolean isParentViewType(int viewType) {
return viewType == PARENT_NORMAL;
}
}
DriverSchedule.java
package com.rtstl.expandablelistview.model;
import android.databinding.BaseObservable;
import com.bignerdranch.expandablerecyclerview.model.Parent;
import com.google.gson.annotations.Expose;
import com.google.gson.annotations.SerializedName;
import java.util.List;
/**
* Created by User1 on 09-03-2018.
*/
public class DriverSchedule extends BaseObservable {
#SerializedName("status")
#Expose
public String status;
#SerializedName("msg")
#Expose
public String msg;
#SerializedName("data")
#Expose
public Data data;
public String getStatus() {
return status;
}
public void setStatus(String status) {
this.status = status;
}
public String getMsg() {
return msg;
}
public void setMsg(String msg) {
this.msg = msg;
}
public Data getData() {
return data;
}
public void setData(Data data) {
this.data = data;
}
public class Data {
#SerializedName("schedules")
#Expose
List<Schedules> sclist;
#SerializedName("driver_details")
#Expose
Driver_details driver_details;
public List<Schedules> getSclist() {
return sclist;
}
public void setSclist(List<Schedules> sclist) {
this.sclist = sclist;
}
public Driver_details getDriver_details() {
return driver_details;
}
public void setDriver_details(Driver_details driver_details) {
this.driver_details = driver_details;
}
}
public class Schedules implements Parent<Alloted_kids> {
#SerializedName("is_active")
#Expose
public String is_active;
#SerializedName("route_details")
#Expose
public Route_details route_details;
#SerializedName("alloted_kids")
#Expose
public List<Alloted_kids> alloted_kids;
public String getIs_active() {
return is_active;
}
public void setIs_active(String is_active) {
this.is_active = is_active;
}
public Route_details getRoute_details() {
return route_details;
}
public void setRoute_details(Route_details route_details) {
this.route_details = route_details;
}
public List<Alloted_kids> getAlloted_kids() {
return alloted_kids;
}
public void setAlloted_kids(List<Alloted_kids> alloted_kids) {
this.alloted_kids = alloted_kids;
}
#Override
public List<Alloted_kids> getChildList() {
return null;
}
#Override
public boolean isInitiallyExpanded() {
return false;
}
}
public class Driver_details {
#SerializedName("driver_details")
#Expose
public Driver_details1 driver_details;
public Driver_details1 getDriver_details() {
return driver_details;
}
public void setDriver_details(Driver_details1 driver_details) {
this.driver_details = driver_details;
}
}
public class Route_details {
#SerializedName("ds_id")
#Expose
public String ds_id;
#SerializedName("kidpool_route_id")
#Expose
public String kidpool_route_id;
public String getDs_id() {
return ds_id;
}
public void setDs_id(String ds_id) {
this.ds_id = ds_id;
}
public String getKidpool_route_id() {
return kidpool_route_id;
}
public void setKidpool_route_id(String kidpool_route_id) {
this.kidpool_route_id = kidpool_route_id;
}
}
public class Alloted_kids {
#SerializedName("kid_name")
#Expose
public String kid_name;
#SerializedName("kid_image")
#Expose
public String kid_image;
public String getKid_name() {
return kid_name;
}
public void setKid_name(String kid_name) {
this.kid_name = kid_name;
}
public String getKid_image() {
return kid_image;
}
public void setKid_image(String kid_image) {
this.kid_image = kid_image;
}
}
public class Driver_details1 {
#SerializedName("driver_id")
#Expose
public String driver_id;
#SerializedName("driver_name")
#Expose
public String driver_name;
public String getDriver_id() {
return driver_id;
}
public void setDriver_id(String driver_id) {
this.driver_id = driver_id;
}
public String getDriver_name() {
return driver_name;
}
public void setDriver_name(String driver_name) {
this.driver_name = driver_name;
}
}
}
KidViewHolder.java
package com.rtstl.expandablelistview.adapter;
import android.support.annotation.NonNull;
import android.view.View;
import android.widget.TextView;
import com.bignerdranch.expandablerecyclerview.ChildViewHolder;
import com.rtstl.expandablelistview.R;
import com.rtstl.expandablelistview.model.DriverSchedule;
class KidViewHolder extends ChildViewHolder{
private TextView mIngredientTextView;
public KidViewHolder(#NonNull View itemView) {
super(itemView);
mIngredientTextView = (TextView) itemView.findViewById(R.id.tv_childname);
}
public void bind(#NonNull DriverSchedule.Alloted_kids ingredient) {
mIngredientTextView.setText(ingredient.getKid_name());
}
}
RouteViewHolder.java
package com.rtstl.expandablelistview.adapter;
import android.annotation.SuppressLint;
import android.os.Build;
import android.support.annotation.NonNull;
import android.view.View;
import android.view.animation.RotateAnimation;
import android.widget.ImageView;
import android.widget.TextView;
import com.bignerdranch.expandablerecyclerview.ParentViewHolder;
import com.rtstl.expandablelistview.R;
import com.rtstl.expandablelistview.model.DriverSchedule;
class RouteViewHolder extends ParentViewHolder {
private static final float INITIAL_POSITION = 0.0f;
private static final float ROTATED_POSITION = 180f;
#NonNull
private final ImageView mArrowExpandImageView;
private TextView mRecipeTextView;
public RouteViewHolder(#NonNull View itemView) {
super(itemView);
mRecipeTextView = (TextView) itemView.findViewById(R.id.group_name);
mArrowExpandImageView = (ImageView) itemView.findViewById(R.id.iv_exp);
}
public void bind(#NonNull DriverSchedule.Schedules recipe) {
mRecipeTextView.setText(recipe.getRoute_details().getKidpool_route_id());
}
#SuppressLint("NewApi")
#Override
public void setExpanded(boolean expanded) {
super.setExpanded(expanded);
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.HONEYCOMB) {
if (expanded) {
mArrowExpandImageView.setRotation(ROTATED_POSITION);
} else {
mArrowExpandImageView.setRotation(INITIAL_POSITION);
}
}
}
#Override
public void onExpansionToggled(boolean expanded) {
super.onExpansionToggled(expanded);
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.HONEYCOMB) {
RotateAnimation rotateAnimation;
if (expanded) { // rotate clockwise
rotateAnimation = new RotateAnimation(ROTATED_POSITION,
INITIAL_POSITION,
RotateAnimation.RELATIVE_TO_SELF, 0.5f,
RotateAnimation.RELATIVE_TO_SELF, 0.5f);
} else { // rotate counterclockwise
rotateAnimation = new RotateAnimation(-1 * ROTATED_POSITION,
INITIAL_POSITION,
RotateAnimation.RELATIVE_TO_SELF, 0.5f,
RotateAnimation.RELATIVE_TO_SELF, 0.5f);
}
rotateAnimation.setDuration(200);
rotateAnimation.setFillAfter(true);
mArrowExpandImageView.startAnimation(rotateAnimation);
}
}
}
See this issue it seems that your list has some null values
https://github.com/bignerdranch/expandable-recycler-view/issues/321
I ran your code and logged your list you have null values in a list
check this method
#Override
public List<Alloted_kids> getChildList() {
return null;
}
this method should return non null value this method only causing problem
use return Collections.emptyList(); instead of return null there
I'm trying to follow this blog post to try and get two way data binding to work for a custom component (A constraint view with an EditText in it).
I'm able to get two standard EditText components to be in sync (both ways) with my model, but I'm having trouble getting the changes in my custom component to flow into my model (although one way data binding works).
My model:
public class Model extends BaseObservable {
private String value;
#Bindable
public String getValue() {
return value;
}
public void setValue(String value) {
this.value = value;
notifyPropertyChanged(company.com.databinding.BR.value);
}
public Model() {
value = "Value";
}
}
Activity:
#InverseBindingMethods({
#InverseBindingMethod(
type = CustomComponent.class,
attribute = "value",
method = "getValue")
})
public class MainActivity extends AppCompatActivity {
#BindingAdapter("value")
public static void setColor(CustomComponent view, String value) {
if (!value.equals(view.getValue())) {
view.setValue(value);
}
}
#BindingAdapter(
value = {"onValueChange", "valueAttrChanged"},
requireAll = false
)
public static void setListeners(CustomComponent view,
final ValueChangeListener onValueChangeListener,
final InverseBindingListener inverseBindingListener) {
ValueChangeListener newListener;
if (inverseBindingListener == null) {
newListener = onValueChangeListener;
} else {
newListener = new ValueChangeListener() {
#Override
public void onValueChange(CustomComponent view,
String value) {
if (onValueChangeListener != null) {
onValueChangeListener.onValueChange(view,
value);
}
inverseBindingListener.onChange();
}
};
}
ValueChangeListener oldListener =
ListenerUtil.trackListener(view, newListener,
R.id.textWatcher);
if (oldListener != null) {
view.removeListener(oldListener);
}
if (newListener != null) {
view.addListener(newListener);
}
}
#Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
//setContentView(R.layout.activity_main);
ActivityMainBinding binding = DataBindingUtil.setContentView(this, R.layout.activity_main);
binding.setModel(new Model());
}
}
Custom component:
public class CustomComponent extends ConstraintLayout {
private String value;
private EditText txt;
private TextWatcher textWatcher;
ValueChangeListener listener;
public String getValue() {
return value;
}
public void setValue(String value) {
this.value = value;
if (txt != null) {
txt.setText(value);
}
}
public CustomComponent(Context context) {
super(context);
init(context);
}
public CustomComponent(Context context, AttributeSet attrs) {
super(context, attrs);
init(context, attrs);
}
public CustomComponent(Context context, AttributeSet attrs, int defStyle) {
super(context, attrs, defStyle);
init(context, attrs);
}
private void init(Context context) {
}
private void init(Context context, AttributeSet attrs) {
View.inflate(context, R.layout.custom_component, this);
txt = findViewById(R.id.txt_box);
final CustomComponent self = this;
textWatcher = new TextWatcher() {
#Override
public void beforeTextChanged(CharSequence charSequence, int i, int i1, int i2) {
}
#Override
public void onTextChanged(CharSequence charSequence, int i, int i1, int i2) {
}
#Override
public void afterTextChanged(Editable editable) {
if (listener != null) {
listener.onValueChange(self, editable.toString());
}
}
};
txt.addTextChangedListener(textWatcher);
}
public void addListener(ValueChangeListener listener) {
this.listener = listener;
}
public void removeListener(ValueChangeListener listener) {
this.listener = null;
}
}
public interface ValueChangeListener {
public void onValueChange(CustomComponent view, String value);
}
I think the section "Hooking The Event" in that post has gone completely over my head; I really only needed a simple setter and getter for the component, and so couldn't quite understand what was being done in that BindingAdapter. Of all of them I think it's this line that I don't get at all:
ValueChangeListener oldListener =
ListenerUtil.trackListener(view, newListener,
R.id.textWatcher);
Demo at: https://github.com/indgov/data_binding
Sorry that the ListenerUtil was confusing. That's only useful when your component supports multiple listeners. In that case, you can't just set a new listener, you must remove the old one and add the new one. ListenerUtil helps you track the old listener so it can be removed. In your case, it can be simplified:
#BindingAdapter(
value = {"onValueChange", "valueAttrChanged"},
requireAll = false
)
public static void setListeners(CustomComponent view,
final ValueChangeListener onValueChangeListener,
final InverseBindingListener inverseBindingListener) {
ValueChangeListener newListener;
if (inverseBindingListener == null) {
newListener = onValueChangeListener;
} else {
newListener = new ValueChangeListener() {
#Override
public void onValueChange(CustomComponent view,
String value) {
if (onValueChangeListener != null) {
onValueChangeListener.onValueChange(view,
value);
}
inverseBindingListener.onChange();
}
};
}
view.setListener(newListener);
}
and then replace addListener() with setListener() and you don't need the removeListener() because you can always set the listener to null.
The problem you're seeing is in your component:
public String getValue() {
return value;
}
You're returning the value that was last set by the setter and not the value that is in the EditText. To solve this:
public String getValue() {
return txt.getText().toString();
}
I am having problem with fragments.I am developing an app like 360 security and i am having problem implementing fragments ,getting "Binary XML file line #1: Error inflating class fragment".
Here is my xml file.I have tried every solution please help me.
Thanks in advance.
<fragment xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:tools="http://schemas.android.com/tools"
android:id="#+id/item_list"
android:name="sabby.completesecurity.MainListFragment"
android:layout_width="match_parent"
android:layout_height="match_parent"
tools:context=".ItemListActivity"
tools:layout="#android:layout/list_content" />
This is my activity file:
package sabby.completesecurity;
import android.app.Activity;
import android.app.AlertDialog;
import android.content.BroadcastReceiver;
import android.content.Context;
import android.content.Intent;
import android.content.IntentFilter;
import android.os.Bundle;
import android.support.v4.app.FragmentActivity;
import android.view.Menu;
import android.view.MenuItem;
import android.widget.FrameLayout;
import android.widget.ImageView;
import sabby.completesecurity.R;
public class MainActivityCache extends FragmentActivity implements sabby.completesecurity.MainCallbacks {
private boolean mIsDualPane;
private boolean mIsArtShowed = false;
#Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main_cache);
mIsDualPane = findViewById(R.id.item_detail_container) != null;
//Show an art when no fragment is showed, we make sure no detail fragment is present.
if (mIsDualPane && getFragmentManager().findFragmentByTag(sabby.completesecurity.DetailFragment.FRAGMENT_TAG) == null) {
ImageView imageView = new ImageView(this);
imageView.setImageResource(R.mipmap.ic_launcher);
imageView.setScaleType(ImageView.ScaleType.FIT_CENTER);
((FrameLayout) findViewById(R.id.item_detail_container)).addView(imageView);
mIsArtShowed = true;
}
}
#Override
public void onItemSelected(String packageName) {
if (mIsDualPane) {
//Hide art when a fragment is showed.
if (mIsArtShowed) {
((FrameLayout) findViewById(R.id.item_detail_container)).removeAllViews();
mIsArtShowed = false;
}
getFragmentManager()
.beginTransaction()
.replace(R.id.item_detail_container, DetailFragment.getInstance(packageName), DetailFragment.FRAGMENT_TAG)
.commit();
} else {
Intent intent = new Intent(this, DetailActivity.class);
intent.putExtra(DetailFragment.EXTRA_PACKAGE_NAME, packageName);
startActivity(intent);
}
}
#Override
protected void onStart() {
super.onStart();
registerReceiver(receiver, getIntentFilter());
}
#Override
protected void onStop() {
super.onStop();
unregisterReceiver(receiver);
}
private void showAboutDialog() {
new AlertDialog.Builder(this)
.setTitle("About")
.setView(getLayoutInflater().inflate(R.layout.about_dialog_message, null))
.setNegativeButton(android.R.string.ok, null)
.show();
}
/**
* Used to update the list if a package is added or removed.
*/
private IntentFilter getIntentFilter() {
IntentFilter filter = new IntentFilter();
filter.addDataScheme("package");
filter.addAction(Intent.ACTION_PACKAGE_CHANGED);
filter.addAction(Intent.ACTION_PACKAGE_REPLACED);
filter.addAction(Intent.ACTION_PACKAGE_FULLY_REMOVED);
filter.addAction(Intent.ACTION_PACKAGE_ADDED);
return filter;
}
enter code here
BroadcastReceiver receiver = new BroadcastReceiver() {
#Override
public void onReceive(Context context, Intent intent) {
MainListFragment mainListFragment = (MainListFragment) getFragmentManager().findFragmentById(R.id.item_list);
if (mainListFragment != null)
mainListFragment.loadList();
}
};
}
MainListFragment.java
package sabby.completesecurity;
import android.app.ActionBar;
import android.app.Activity;
import android.app.ListFragment;
import android.app.ProgressDialog;
import android.content.Context;
import android.content.pm.ApplicationInfo;
import android.content.pm.IPackageStatsObserver;
import android.content.pm.PackageInfo;
import android.content.pm.PackageManager;
import android.content.pm.PackageStats;
import android.graphics.drawable.Drawable;
import android.os.AsyncTask;
import android.os.Bundle;
import android.os.RemoteException;
import android.text.format.Formatter;
import android.view.LayoutInflater;
import android.view.Menu;
import android.view.MenuInflater;
import android.view.MenuItem;
import android.view.View;
import android.view.ViewGroup;
import android.widget.AdapterView;
import android.widget.BaseAdapter;
import android.widget.ImageView;
import android.widget.SectionIndexer;
import android.widget.Spinner;
import android.widget.TextView;
import sabby.completesecurity.utils.Utils;
import java.lang.reflect.InvocationTargetException;
import java.lang.reflect.Method;
import java.text.SimpleDateFormat;
import java.util.ArrayList;
import java.util.Collections;
import java.util.Comparator;
import java.util.Date;
import java.util.List;
public class MainListFragment extends ListFragment implements AdapterView.OnItemClickListener, AdapterView.OnItemSelectedListener {
private static final int SORT_NAME = 0;
private static final int SORT_PKG = 1;
private static final int SORT_DOMAIN = 2;
private static final int SORT_INSTALLATION = 3;
private static final int SORT_SIZE = 4;
private static final String INSTANCE_STATE_SORT_BY = "sort_by";
private Adapter mAdapter;
private List<Item> mItemList = new ArrayList<Item>();
private int mOnSizeFinishedItemCount;
private PackageManager mPackageManager;
private ProgressDialog mProgressDialog;
private LayoutInflater mLayoutInflater;
private MainCallbacks mCallbacks;
private Context mContext;
private Async mAsyncLoader;
private Spinner mSpinner;
private boolean mSpinnerListenerAuthorized;
private SimpleDateFormat mSimpleDateFormat;
private int mSortBy = 0;
class Item {
ApplicationInfo applicationInfo;
String label;
Long date;
Long size = -1L;
}
private int mColorGrey1;
private int mColorGrey2;
#Override
public void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setHasOptionsMenu(true);
mProgressDialog = new ProgressDialog(mContext);
mProgressDialog.setTitle(R.string.loading_apps);
mProgressDialog.setProgressStyle(ProgressDialog.STYLE_HORIZONTAL);
mProgressDialog.setCancelable(false);
//Used to prevent message not showing later
mProgressDialog.setMessage("");
mPackageManager = mContext.getPackageManager();
mSimpleDateFormat = new SimpleDateFormat("dd/MM/yyyy hh:mm:ss");
mColorGrey1 = getResources().getColor(R.color.grey_1);
mColorGrey2 = getResources().getColor(R.color.grey_2);
ActionBar actionBar = getActivity().getActionBar();
actionBar.setDisplayShowCustomEnabled(true);
mSpinner = new Spinner(actionBar.getThemedContext());
SpinnerAdapter spinnerAdapter = new SpinnerAdapter(actionBar.getThemedContext(),
R.array.sort_spinner_items, android.R.layout.simple_list_item_1);
mSpinner.setAdapter(spinnerAdapter);
mSpinnerListenerAuthorized = false;
mSpinner.setOnItemSelectedListener(this);
ActionBar.LayoutParams layoutParams = new ActionBar.LayoutParams(ViewGroup.LayoutParams.MATCH_PARENT,
ViewGroup.LayoutParams.MATCH_PARENT);
actionBar.setCustomView(mSpinner, layoutParams);
if (savedInstanceState != null)
setSortBy(savedInstanceState.getInt(INSTANCE_STATE_SORT_BY, -1), false);
}
#Override
public void onStart() {
super.onStart();
mSpinner.setSelection(mSortBy);
}
#Override
public void onSaveInstanceState(Bundle outState) {
super.onSaveInstanceState(outState);
outState.putInt(INSTANCE_STATE_SORT_BY, mSortBy);
}
private void onTaskEnded(List<Item> list) {
RetainedFragment retainedFragment = (RetainedFragment) getFragmentManager().findFragmentByTag(RetainedFragment.FRAGMENT_TAG);
retainedFragment.setList(list);
mItemList = list;
mAdapter.notifyDataSetChanged();
if (getListView().getAdapter() == null)
setListAdapter(mAdapter);
}
#Override
public void onViewCreated(View view, Bundle savedInstanceState) {
super.onViewCreated(view, savedInstanceState);
getListView().setOnItemClickListener(this);
getListView().setFastScrollEnabled(true);
mAdapter = new Adapter();
RetainedFragment retainedFragment = (RetainedFragment) getFragmentManager()
.findFragmentByTag(RetainedFragment.FRAGMENT_TAG);
if (retainedFragment == null) {
retainedFragment = new RetainedFragment();
getFragmentManager()
.beginTransaction()
.add(retainedFragment, RetainedFragment.FRAGMENT_TAG)
.commit();
}
if (retainedFragment.getList() != null) {
onTaskEnded(retainedFragment.getList());
mOnSizeFinishedItemCount = mItemList.size();
//Notify spinner that size sort is available
SpinnerAdapter adapter = (SpinnerAdapter) mSpinner.getAdapter();
adapter.notifyDataSetChanged();
} else
loadList();
}
public void loadList() {
mAsyncLoader = new Async();
mAsyncLoader.execute();
}
#Override
public void onAttach(Activity activity) {
super.onAttach(activity);
mCallbacks = (MainCallbacks) activity;
mContext = activity;
mLayoutInflater = activity.getLayoutInflater();
}
#Override
public void onDetach() {
super.onDetach();
if (mAsyncLoader != null)
mAsyncLoader.cancel(true);
mCallbacks = null;
mContext = null;
mLayoutInflater = null;
}
#Override
public void onItemClick(AdapterView<?> adapterView, View view, int i, long l) {
if (mCallbacks != null)
mCallbacks.onItemSelected(mItemList.get(i).applicationInfo.packageName);
}
#Override
public void onCreateOptionsMenu(Menu menu, MenuInflater inflater) {
inflater.inflate(R.menu.fragment_main_list, menu);
}
#Override
public boolean onOptionsItemSelected(MenuItem item) {
switch (item.getItemId()) {
case R.id.action_refresh:
loadList();
return true;
}
return super.onOptionsItemSelected(item);
}
#Override
public void onItemSelected(AdapterView<?> adapterView, View view, int i, long l) {
if (mSpinnerListenerAuthorized)
setSortBy(i, true);
mSpinnerListenerAuthorized = true;
}
/**
* Sort main list if provided value is valid.
* #param sort Must be one of SORT_*
* #param checkViews Set if views have to be updated, eg. when restoring state, views aren't
* created yet, so value must be false
*/
public void setSortBy(int sort, boolean checkViews) {
if (sort >= SORT_NAME && sort <= SORT_SIZE) {
mSortBy = sort;
if (checkViews) {
checkFastScroll();
sortApplicationList(mItemList, mSortBy);
mAdapter.notifyDataSetChanged();
}
}
}
#Override
public void onNothingSelected(AdapterView<?> adapterView) {
}
private void checkFastScroll() {
getListView().setFastScrollEnabled(mSortBy == SORT_NAME);
}
public void sortApplicationList(List<Item> list, final int sortBy) {
Collections.sort(list, new Comparator<Item>() {
#Override
public int compare(Item item1, Item item2) {
switch (sortBy) {
case SORT_NAME:
return item1.label.compareTo(item2.label);
case SORT_PKG:
return item1.applicationInfo.packageName.compareTo(item2.applicationInfo.packageName);
case SORT_DOMAIN:
boolean isSystem1 = (item1.applicationInfo.flags & ApplicationInfo.FLAG_SYSTEM) != 0;
boolean isSystem2 = (item2.applicationInfo.flags & ApplicationInfo.FLAG_SYSTEM) != 0;
return Utils.compareBooleans(isSystem1, isSystem2);
case SORT_INSTALLATION:
//Sort in decreasing order
return -item1.date.compareTo(item2.date);
case SORT_SIZE:
return -item1.size.compareTo(item2.size);
default:
return 0;
}
}
});
}
/**
* This method is called by each item when it has finished retrieving its size
* When all items have finished, we set size sort available in spinner, and invalidate
* main list to display sizes in UI.
*/
private void onItemFinishedSizeProcess() {
mOnSizeFinishedItemCount ++;
if (mOnSizeFinishedItemCount == mItemList.size()) {
getActivity().runOnUiThread(new Runnable() {
#Override
public void run() {
SpinnerAdapter adapter = (SpinnerAdapter) mSpinner.getAdapter();
adapter.notifyDataSetChanged();
mAdapter.notifyDataSetChanged();
}
});
}
}
class Adapter extends BaseAdapter implements SectionIndexer {
class ViewHolder {
ImageView icon;
TextView label;
TextView packageName;
TextView version;
TextView isSystemApp;
TextView date;
TextView size;
IconAsyncTask iconLoader;
}
String sections = "ABCDEFGHIJKLMNOPQRSTUVWXYZ";
#Override
public int getCount() {
return mItemList.size();
}
#Override
public Object getItem(int i) {
return mItemList.get(i);
}
#Override
public long getItemId(int i) {
return i;
}
#Override
public View getView(int i, View view, ViewGroup viewGroup) {
ViewHolder holder;
if (view == null) {
view = mLayoutInflater.inflate(R.layout.main_list_item, null);
holder = new ViewHolder();
holder.icon = (ImageView) view.findViewById(R.id.icon);
holder.label = (TextView) view.findViewById(R.id.label);
holder.packageName = (TextView) view.findViewById(R.id.packageName);
holder.version = (TextView) view.findViewById(R.id.version);
holder.isSystemApp = (TextView) view.findViewById(R.id.isSystem);
holder.date = (TextView) view.findViewById(R.id.date);
holder.size = (TextView) view.findViewById(R.id.size);
view.setTag(holder);
} else {
holder = (ViewHolder) view.getTag();
holder.iconLoader.cancel(true);
}
view.setBackgroundColor(i % 2 == 0 ? mColorGrey2 : mColorGrey1);
Item item = mItemList.get(i);
ApplicationInfo info = item.applicationInfo;
try {
PackageInfo packageInfo = mPackageManager.getPackageInfo(info.packageName, 0);
holder.version.setText(packageInfo.versionName);
Date date = new Date(packageInfo.firstInstallTime);
holder.date.setText(mSimpleDateFormat.format(date));
} catch (PackageManager.NameNotFoundException e) {
//Do nothing
}
holder.iconLoader = new IconAsyncTask(holder.icon, info);
holder.iconLoader.execute();
holder.label.setText(info.loadLabel(mPackageManager));
holder.packageName.setText(info.packageName);
boolean isSystemApp = (info.flags & ApplicationInfo.FLAG_SYSTEM) != 0;
holder.isSystemApp.setText(isSystemApp ? getString(R.string.system) : getString(R.string.user));
if (item.size != -1L)
holder.size.setText(Formatter.formatFileSize(getActivity(), item.size));
return view;
}
#Override
public int getPositionForSection(int section) {
for (int i = 0; i < this.getCount(); i++) {
String item = mItemList.get(i).label;
if (item.charAt(0) == sections.charAt(section))
return i;
}
return 0;
}
#Override
public int getSectionForPosition(int i) {
return 0;
}
#Override
public Object[] getSections() {
String[] sectionsArr = new String[sections.length()];
for (int i = 0; i < sections.length(); i++)
sectionsArr[i] = "" + sections.charAt(i);
return sectionsArr;
}
class IconAsyncTask extends AsyncTask<Void, Integer, Drawable> {
ImageView imageView;
ApplicationInfo info;
IconAsyncTask(ImageView imageView, ApplicationInfo info) {
this.imageView = imageView;
this.info = info;
}
#Override
protected void onPreExecute() {
super.onPreExecute();
imageView.setVisibility(View.INVISIBLE);
}
#Override
protected Drawable doInBackground(Void... voids) {
if (!isCancelled())
return info.loadIcon(mPackageManager);
return null;
}
#Override
protected void onPostExecute(Drawable drawable) {
super.onPostExecute(drawable);
imageView.setImageDrawable(drawable);
imageView.setVisibility(View.VISIBLE);
}
}
}
class SpinnerAdapter extends BaseAdapter {
private Context mContext;
private int mLayoutResId;
private String[] mItems;
public SpinnerAdapter(Context themedContext, int arrayResId, int layoutResId) {
mContext = themedContext;
mItems = themedContext.getResources().getStringArray(arrayResId);
mLayoutResId = layoutResId;
}
#Override
public int getCount() {
return mItems.length;
}
#Override
public Object getItem(int i) {
return null;
}
#Override
public long getItemId(int i) {
return 0;
}
//It make no sense to implement recycled view system because there is only 5 items in list
#Override
public View getView(int i, View view, ViewGroup viewGroup) {
view = View.inflate(mContext, mLayoutResId, null);
if (view instanceof TextView)
((TextView) view).setText(mItems[i]);
return view;
}
/**
* Set sort_by_size item disabled if all items haven't retrieved them size.
*/
#Override
public boolean isEnabled(int position) {
return position != SORT_SIZE || mItemList != null && mOnSizeFinishedItemCount == mItemList.size();
}
}
class Async extends AsyncTask<Void, Async.Progress, List<Item>> {
class Progress {
String label;
int totalSize;
}
#Override
protected void onPreExecute() {
super.onPreExecute();
mProgressDialog.show();
}
#Override
protected List<Item> doInBackground(Void... voids) {
List<ApplicationInfo> applicationInfos = mPackageManager.getInstalledApplications(PackageManager.GET_META_DATA);
Progress progress = new Progress();
progress.totalSize = applicationInfos.size();
List<Item> itemList = new ArrayList<Item>(applicationInfos.size());
mOnSizeFinishedItemCount = 0;
for (ApplicationInfo applicationInfo : applicationInfos) {
if (isCancelled())
break;
Item item = new Item();
item.applicationInfo = applicationInfo;
String label = applicationInfo.loadLabel(mPackageManager).toString();
item.label = label;
try {
item.date = mPackageManager.getPackageInfo(applicationInfo.packageName, 0).firstInstallTime;
} catch (PackageManager.NameNotFoundException e) {
item.date = 0L;
}
itemList.add(item);
getItemSize(item);
progress.label = label;
publishProgress(progress);
}
sortApplicationList(itemList, mSortBy);
return itemList;
}
private void getItemSize(final Item item) {
try {
Method getPackageSizeInfo = mPackageManager.getClass().getMethod(
"getPackageSizeInfo", String.class, IPackageStatsObserver.class);
getPackageSizeInfo.invoke(mPackageManager, item.applicationInfo.packageName, new IPackageStatsObserver.Stub() {
#Override
public void onGetStatsCompleted(final PackageStats pStats, boolean succeeded)
throws RemoteException {
if (succeeded)
item.size = pStats.codeSize + pStats.cacheSize + pStats.dataSize
+ pStats.externalCodeSize + pStats.externalCacheSize + pStats.externalDataSize
+ pStats.externalMediaSize + pStats.externalObbSize;
else
item.size = -1L;
onItemFinishedSizeProcess();
}
});
} catch (NoSuchMethodException e) {
e.printStackTrace();
onItemFinishedSizeProcess();
} catch (IllegalAccessException e) {
e.printStackTrace();
onItemFinishedSizeProcess();
} catch (InvocationTargetException e) {
e.printStackTrace();
onItemFinishedSizeProcess();
}
}
#Override
protected void onProgressUpdate(Progress... values) {
super.onProgressUpdate(values);
Progress progress = values[0];
mProgressDialog.setMessage(progress.label);
if (mProgressDialog.getMax() == 100)
mProgressDialog.setMax(progress.totalSize);
mProgressDialog.incrementProgressBy(1);
}
#Override
protected void onPostExecute(List<Item> list) {
super.onPostExecute(list);
mProgressDialog.hide();
onTaskEnded(list);
}
#Override
protected void onCancelled(List<Item> list) {
super.onCancelled(list);
mProgressDialog.hide();
}
}
}
In the xml that you have posted,replace the TAG <Fragment> with <LinearLayout> or <RelativeLayout> and inside this you simply show what you want to display in you UI
Make sure you extend Fragment class in you fragment activity
Could you post the code for your fragment? The problem could be with your import statements. You have 'import android.support.v4.app.FragmentActivity'. Do you have 'import android.support.v4.app.Fragment' in your fragment file?
So i have a list of alarms and i bind that to the listview, lstAlarms. In my custom ListView layout, i also have a switch, which i want to be set programmatically according to the status of the alarm. I want to do this right after the ListView is just displayed.
Please see my codes below.
The method that display the ListView is DisplayAlarmList().
The method that im trying to use to set the states of the switches is InitSwitches(), which is being called inside of DisplayAlarmList().
DisplayAlarmList() is called in the onCreate() method.
public void DisplayAlarmList()
{
final String[] columns = {Database.getAlarmID(), Database.getAlarmTime(), Database.getAlarmName(), Database.getAlarmStatus(), Database.getAlarmRepeats()};
Cursor c = Database.selectAlarm(db, Database.getTableName(), columns, null, null, null, null, null);
int[] to = new int[]{
R.id.alarmID,
R.id.alarmTime,
R.id.alarmName,
R.id.alarmStatus,
R.id.alarmRepeats,
};
SimpleCursorAdapter ca = new SimpleCursorAdapter(this,
R.layout.alarm_info,
c,
columns,
to,
0);
lstAlarm.setAdapter(ca);
InitSwitches();
lstAlarm.setOnItemClickListener(new OnItemClickListener()
{
#Override
public void onItemClick(AdapterView<?> listView, View view, int position, long id)
{
Alarm alarm = new Alarm();
Cursor selectedCursor = (Cursor) listView.getItemAtPosition(position);
Switch s = (Switch) view.findViewById(R.id.alarmSwitch);
String whereArgs = Integer.toString(selectedCursor.getInt(selectedCursor.getColumnIndexOrThrow(Database.getAlarmID())));
Cursor data = Database.RawQuery(db, "SELECT * FROM " + Database.getTableName() + " WHERE " + Database.getAlarmID() + " = " + whereArgs);
if (data.moveToFirst())
{
alarm.setAlarmID(data.getString(data.getColumnIndexOrThrow(Database.getAlarmID())));
alarm.setAlarmName(data.getString(data.getColumnIndexOrThrow(Database.getAlarmName())));
alarm.setAlarmTime(data.getString(data.getColumnIndexOrThrow(Database.getAlarmTime())));
alarm.setAlarmSound(data.getString(data.getColumnIndexOrThrow(Database.getAlarmSound())));
alarm.setAlarmRepeats(data.getString(data.getColumnIndexOrThrow(Database.getAlarmRepeats())));
alarm.setAlarmStatus(data.getString(data.getColumnIndexOrThrow(Database.getAlarmStatus())));
}
Intent i = new Intent(view.getContext(), ScreenEdit.class);
i.putExtra("editAlarm", new Gson().toJson(alarm));
startActivityForResult(i, EDIT_ALARM);
}
});
}
public void InitSwitches()
{
View v;
Switch s;
int index = 0;
String query = "Select " + Database.getAlarmID() + ", " + Database.getAlarmStatus() + " FROM " + Database.getTableName();
Cursor statuses = Database.RawQuery(db, query);
while (statuses.moveToNext())
{
v = lstAlarm.getAdapter().getView(index++, null, null);
s = (Switch) v.findViewById(R.id.alarmSwitch);
if (statuses.getString(statuses.getColumnIndexOrThrow(Database.getAlarmStatus())).equals("ON")){
s.toggle();
}
else{
}
}
}
ScreenShot of app
As you can see from the screen shot, the switch in the second row is supposed to be ON, but it's not. I tried InvilidateView() too but didnt work. Please help.
The problem is that ListView is one of those views that is being constantly redrawn, meaning that this is bad, becuase it will lose the state when redrawn
if (statuses.getString(statuses.getColumnIndexOrThrow(Database.getAlarmStatus())).equals("ON")){
s.toggle();
}
First create AlarmItem... you should make it according to your needs. Here is mine
public class AlarmItem {
private String alarmName;
private String alarmDescption;
private boolean state;
private long id;
public AlarmItem(String alarmName, String alarmDescption, long id, boolean state) {
this.alarmName = alarmName;
this.alarmDescption = alarmDescption;
this.id = id;
this.state = state;
}
public long getId() {
return id;
}
public void setId(long id) {
this.id = id;
}
public String getAlarmName() {
return alarmName;
}
public void setAlarmName(String alarmName) {
this.alarmName = alarmName;
}
public String getAlarmDescption() {
return alarmDescption;
}
public void setAlarmDescption(String alarmDescption) {
this.alarmDescption = alarmDescption;
}
public boolean getState() {
return state;
}
public void setState(boolean state) {
this.state = state;
}
}
Now we will need a CustomSwitch class because of this: LINK
import android.content.Context;
import android.util.AttributeSet;
import android.widget.Switch;
public class CustomSwitch extends Switch {
private OnCheckedChangeListener mListener;
public CustomSwitch(Context context) {
super(context);
}
public CustomSwitch(Context context, AttributeSet attrs) {
super(context, attrs);
}
public CustomSwitch(Context context, AttributeSet attrs, int defStyleAttr) {
super(context, attrs, defStyleAttr);
}
public CustomSwitch(Context context, AttributeSet attrs, int defStyleAttr, int defStyleRes) {
super(context, attrs, defStyleAttr, defStyleRes);
}
#Override
public void setOnCheckedChangeListener(OnCheckedChangeListener listener) {
// Do not call supper method
mListener = listener;
}
#Override
public void setChecked(boolean checked) {
super.setChecked(checked);
if (mListener != null) {
mListener.onCheckedChanged(this, checked);
}
}
public void setCheckedProgrammatically(boolean checked) {
// You can call super method, it doesn't have a listener... he he :)
super.setChecked(checked);
}
}
create a layout file and name it alarm_list_item.xml
<?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="50dp"
android:orientation="vertical">
<com.example.alarm.list.CustomSwitch
android:id="#+id/alarmSwitch"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_alignParentRight="true"
android:layout_centerVertical="true"
android:layout_marginRight="10dp"/>
<LinearLayout
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_marginLeft="5dp"
android:layout_toLeftOf="#id/alarmSwitch"
android:orientation="vertical">
<TextView
android:id="#+id/tvAlarmName"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_marginTop="5dp"
android:text="Alarm Name"
android:textSize="18sp"/>
<TextView
android:id="#+id/tvAlarmDesc"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_marginTop="5dp"
android:text="Alarm description"
android:textSize="15sp"/>
</LinearLayout>
</RelativeLayout>
Now the main layout -> activity_main.xml
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:orientation="vertical">
<ListView
android:id="#+id/lvAlarms"
android:layout_width="match_parent"
android:layout_height="match_parent"/>
</LinearLayout>
Now we need an adapter that is responsible for drawing the items and their handling. Name the class AlarmAdapter
import android.content.Context;
import android.view.LayoutInflater;
import android.view.View;
import android.view.ViewGroup;
import android.widget.BaseAdapter;
import android.widget.CompoundButton;
import android.widget.TextView;
import java.util.List;
public class AlarmAdapter extends BaseAdapter {
private List<AlarmItem> listOfAlarms;
private Context context;
private OnAlarmCheckedChangeListener mCallback;
// avoid constant allocation
private View tmpView;
private AlarmItemViewHolder mHolder;
private AlarmItem tmpItem;
public AlarmAdapter(List<AlarmItem> listOfAlarms, Context context, OnAlarmCheckedChangeListener callBack) {
this.listOfAlarms = listOfAlarms;
this.context = context;
mCallback = callBack;
}
#Override
public int getCount() {
return listOfAlarms == null ? 0 : listOfAlarms.size();
}
#Override
public AlarmItem getItem(int i) {
return listOfAlarms == null ? null : listOfAlarms.get(i);
}
#Override
public long getItemId(int i) {
return 0;
}
#Override
public View getView(final int i, View view, ViewGroup viewGroup) {
tmpItem = listOfAlarms.get(i);
if (view == null) {
LayoutInflater inflater = LayoutInflater.from(context);
tmpView = inflater.inflate(R.layout.alarm_list_item, null, false);
mHolder = new AlarmItemViewHolder(tmpView);
tmpView.setTag(mHolder);
}
else {
tmpView = view;
mHolder = (AlarmItemViewHolder) view.getTag();
}
mHolder.getAlarmNameTextView().setText(tmpItem.getAlarmName());
mHolder.getAlarmDescriptionTextView().setText(tmpItem.getAlarmDescption());
mHolder.getSwitch().setCheckedProgrammatically(tmpItem.getState());
mHolder.getSwitch().setOnCheckedChangeListener(new CompoundButton.OnCheckedChangeListener() {
#Override
public void onCheckedChanged(CompoundButton compoundButton, boolean b) {
listOfAlarms.get(i).setState(b);
mCallback.onAlarmStateChanged(listOfAlarms.get(i), i);
}
});
return tmpView;
}
public void clear() {
listOfAlarms.clear();
notifyDataSetChanged();
}
public void refill(List<AlarmItem> listOfAlarms) {
this.listOfAlarms = listOfAlarms;
notifyDataSetChanged();
}
public void toggleAllSwitches() {
for (AlarmItem item : listOfAlarms) {
item.setState(!item.getState());
}
notifyDataSetChanged();
}
public interface OnAlarmCheckedChangeListener {
public void onAlarmStateChanged(AlarmItem item, int postionInList);
}
private class AlarmItemViewHolder {
View base;
CustomSwitch mSwitch;
TextView mAlarmName;
TextView mAlarmDescription;
public AlarmItemViewHolder(View base) {
this.base = base;
}
public CustomSwitch getSwitch() {
if (mSwitch == null) {
mSwitch = (CustomSwitch) base.findViewById(R.id.alarmSwitch);
}
return mSwitch;
}
public TextView getAlarmNameTextView() {
if (mAlarmName == null) {
mAlarmName = (TextView) base.findViewById(R.id.tvAlarmName);
}
return mAlarmName;
}
public TextView getAlarmDescriptionTextView() {
if (mAlarmDescription == null) {
mAlarmDescription = (TextView) base.findViewById(R.id.tvAlarmDesc);
}
return mAlarmDescription;
}
}
}
And now finally the MainActivity
import android.app.ActionBar;
import android.os.Bundle;
import android.os.Handler;
import android.os.Looper;
import android.support.v7.app.ActionBarActivity;
import android.view.View;
import android.widget.AdapterView;
import android.widget.ListView;
import android.widget.Toast;
import java.util.ArrayList;
import java.util.List;
public class MainActivity extends ActionBarActivity implements AdapterView.OnItemClickListener, AlarmAdapter.OnAlarmCheckedChangeListener {
private ListView listView;
private AlarmAdapter adapter;
private Toast toast;
private Handler handler;
private Runnable handlerRunnable;
#Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
// Make a nice actionBar title
ActionBar ab = getActionBar();
if (ab != null) {
ab.setTitle("Alarm List");
}
listView = (ListView) findViewById(R.id.lvAlarms);
// Simulating alarms from database. You need to convert your items to these
List<AlarmItem> alarmsFromDb = new ArrayList<>();
alarmsFromDb.add(new AlarmItem("Alarm 1", "Lalalala", 1, true));
alarmsFromDb.add(new AlarmItem("Alarm 2", "something", 2, false));
alarmsFromDb.add(new AlarmItem("Alarm 3", "gfdgdf", 3, true));
alarmsFromDb.add(new AlarmItem("Alarm 4", "sda", 4, true));
alarmsFromDb.add(new AlarmItem("Alarm 5", "yxcxyc", 5, false));
alarmsFromDb.add(new AlarmItem("Alarm 6", "dsfsd", 6, false));
adapter = new AlarmAdapter(alarmsFromDb, this, this);
listView.setAdapter(adapter);
listView.setOnItemClickListener(this);
// Toggle all switches after 5s... this is what you need, right?
handlerRunnable = new Runnable() {
#Override
public void run() {
adapter.toggleAllSwitches();
showToast("All switches toggeled :)");
}
};
handler = new Handler(Looper.getMainLooper());
handler.postDelayed(handlerRunnable, 5 * 1000);
}
#Override
protected void onDestroy() {
super.onDestroy();
if (handler != null) {
handler.removeCallbacks(handlerRunnable);
handler = null;
handlerRunnable = null;
}
}
private void showToast(String str) {
if (toast != null) {
toast.cancel();
}
toast = Toast.makeText(this, str, Toast.LENGTH_SHORT);
toast.show();
}
#Override
public void onAlarmStateChanged(AlarmItem item, int postionInList) {
String onOff = item.getState() ? "ON" : "OFF";
showToast("Alarm " + item.getAlarmName() + " is: " + onOff);
}
#Override
public void onItemClick(AdapterView<?> parent, View view, int position, long id) {
AlarmItem item = adapter.getItem(position);
showToast("Alarm " + item.getAlarmName() + " clicked");
}
}
Here is the complete android studio project, if you have any problems: LINK
Now all you need to do is convert the results from your database to this list or modify the AlarmItem and the UI. This solution will work and you already have some helpful methods in the adapter.
Happy coding!
does your adapter use the viewholder pattern? If not, the Views that go off screen are reused and may have old switch status that the new row coming in when scrolling should not have.
EDIT I didn't add my XML
I am writing a dialog for tagging selections. The first view is applying tags they have in their db. The next screen is a dialog for adding new tags to their db. I am supplying suggestions for them to use for their tags. I want to filter the list when they begin typing in their tag. I am using two custom CursorAdapters for each screen but they share the same ListView. I am also using a CursorLoader to run my queries in the background both are extended from the support library.
When I open the new tag screen the first time, after the dialog comes up, the list doesn't refresh with the filtered cursor in Android 4.0.3 nor does the quick scroll work. If I switch to the tag view and then back to the new tag dialog it filters and scrolls like it should.. My query works and the code works in Android 2.3.3 every time. I can't figure out why this is not working. Here is my code for the CursorLoader and the Suggestion adapter.
package org.lds.ldssa;
import android.app.AlertDialog;
import android.content.Context;
import android.content.DialogInterface;
import android.content.res.Resources;
import android.database.Cursor;
import android.os.Bundle;
import android.support.v4.app.LoaderManager;
import android.support.v4.content.CursorLoader;
import android.support.v4.content.Loader;
import android.support.v4.widget.SimpleCursorAdapter;
import android.text.Editable;
import android.text.TextWatcher;
import android.util.Log;
import android.view.LayoutInflater;
import android.view.View;
import android.view.inputmethod.InputMethodManager;
import android.widget.*;
import org.lds.ldssa.service.MLDatabase;
import org.lds.ldssa.service.aws.Annotation;
public class TagDialog extends AlertDialog implements LoaderManager.LoaderCallbacks<Cursor> {
private static final String TAG = "ldssa.tagdialog";
public static final int TAGLOADERID = 0;
// View Items
private EditText mEditText;
private ListView mListView;
private TextView mEmptyView;
private ProgressBar mProgressBar;
private ImageButton mNewTagButton;
private ImageButton mSortTagButton;
private TextView mTitle;
private Button mOkButton;
private Button mCancelButton;
private String mTagTitle;
private String mNewTagTitle;
private Annotation mAnnotation;
private ContentFragment mContentFragment;
private boolean isNewTagView;
private static final String KEY_NEWTAGVIEW = "new_tag_view";
private SimpleCursorAdapter mSuggestionAdapter;
private TagListAdapter mTagAdapter;
private MLDatabase mlDatabase;
protected TagDialog(Context context) {
super(context);
}
public void onCreate(Bundle savedInstanceState){
Context context = getContext();
Resources r = context.getResources();
final LayoutInflater inflater = LayoutInflater.from(context);
View view = inflater.inflate(R.layout.dialog_tag, null);
// Main parts of the view
mEditText = (EditText) view.findViewById(R.id.tag_new_tag);
mListView = (ListView) view.findViewById(android.R.id.list);
mProgressBar = (ProgressBar) view.findViewById(R.id.tag_spin_progress_bar);
mEmptyView = (TextView) view.findViewById(android.R.id.empty);
mEmptyView.setVisibility(View.INVISIBLE);
// Titlebar
View titleBar = inflater.inflate(R.layout.dialog_tag_title, null);
mNewTagButton = (ImageButton) titleBar.findViewById(R.id.tag_new_icon);
mSortTagButton = (ImageButton) titleBar.findViewById(R.id.tag_sort_icon);
mTitle = (TextView) titleBar.findViewById(R.id.tag_title);
mTagTitle = r.getString(R.string.tag_dialog_title);
mNewTagTitle = r.getString(R.string.tag_new_dialog_title);
this.setCustomTitle(titleBar);
// Buttons
final String OK = r.getString(R.string.ok);
final String CANCEL = r.getString(R.string.cancel);
this.setButton(BUTTON_POSITIVE, OK, new OnClickListener() {
#Override
public void onClick(DialogInterface dialog, int which) { /*Never Used*/}});
this.setButton(BUTTON_NEGATIVE, CANCEL, new OnClickListener() {
#Override
public void onClick(DialogInterface dialog, int which) { /*Never Used*/}});
// Setup Button Listeners
setOnShowListener(new OnShowListener() {
#Override
public void onShow(DialogInterface dialog) {
Button ok = getButton(BUTTON_POSITIVE);
ok.setOnClickListener(new View.OnClickListener() {
#Override
public void onClick(View v) {
if(isNewTagView){
hideIMM();
setupTagDialog();
mEditText.setText("");
} else {
dismiss();
}
}
});
Button cancel = getButton(BUTTON_NEGATIVE);
cancel.setOnClickListener(new View.OnClickListener() {
#Override
public void onClick(View v) {
if(isNewTagView){
hideIMM();
setupTagDialog();
mEditText.setText("");
} else {
dismiss();
}
}
});
}
});
mNewTagButton.setOnClickListener(new View.OnClickListener() {
#Override
public void onClick(View v) {
setupNewTagDialog();
}
});
mSortTagButton.setOnClickListener(new View.OnClickListener() {
#Override
public void onClick(View v) {
}
});
mEditText.addTextChangedListener(new TextWatcher() {
#Override
public void beforeTextChanged(CharSequence s, int start, int count, int after) {}
#Override
public void onTextChanged(CharSequence s, int start, int before, int count) {}
#Override
public void afterTextChanged(Editable s) {
LoaderManager lm = getLoaderManager();
if(lm != null){
Loader l = lm.getLoader(TAGLOADERID);
if(l != null){
l.forceLoad();
} else {
restartLoader();
}
} else {
restartLoader();
}
}
});
String[] UIBindFrom = {MLDatabase.CL_ID};
int[] UIBindTo = {android.R.id.text1};
mTagAdapter = new TagListAdapter(context, null);
mSuggestionAdapter = new SimpleCursorAdapter(context, android.R.layout.simple_list_item_1, null,
UIBindFrom, UIBindTo, 0);
//Handle Rotations
if(savedInstanceState == null){
//New
setupTagDialog();
} else {
//rotated
isNewTagView = savedInstanceState.getBoolean(KEY_NEWTAGVIEW, false);
if(isNewTagView){
restoreTagState(savedInstanceState);
} else {
restoreNewTagState(savedInstanceState);
}
}
LoaderManager lm = getLoaderManager();
if(lm != null){
lm.initLoader(TAGLOADERID, null, this);
}
this.setView(view);
super.onCreate(savedInstanceState);
}
public void onStart(){
restartLoader();
}
#Override
public Bundle onSaveInstanceState(){
Bundle bundle = super.onSaveInstanceState();
bundle.putBoolean(KEY_NEWTAGVIEW, isNewTagView);
return bundle;
}
#Override
public void onBackPressed(){
if(isNewTagView){
hideIMM();
setupTagDialog();
} else {
this.dismiss();
}
}
private void setupTagDialog() {
isNewTagView = false;
mTitle.setText(mTagTitle);
mNewTagButton.setVisibility(View.VISIBLE);
mSortTagButton.setVisibility(View.VISIBLE);
mEditText.setVisibility(View.GONE);
mListView.setVisibility(View.GONE);
mProgressBar.setVisibility(View.VISIBLE);
mListView.setAdapter(mTagAdapter);
restartLoader();
}
private void setupNewTagDialog() {
isNewTagView = true;
mTitle.setText(mNewTagTitle);
mNewTagButton.setVisibility(View.INVISIBLE);
mSortTagButton.setVisibility(View.INVISIBLE);
mEditText.setVisibility(View.VISIBLE);
mListView.setVisibility(View.GONE);
mProgressBar.setVisibility(View.VISIBLE);
mListView.setAdapter(mSuggestionAdapter);
restartLoader();
}
private void restoreTagState(Bundle bundle) {
setupTagDialog();
}
private void restoreNewTagState(Bundle bundle) {
setupNewTagDialog();
}
public void setAnnotation(Annotation a) {
mAnnotation = a;
}
public void setContentViewInterface(ContentFragment contentFragment) {
mContentFragment = contentFragment;
}
private MLDatabase getDatabase() {
if(mlDatabase == null){
GospelLibraryApplication app = (GospelLibraryApplication) getContext().getApplicationContext();
mlDatabase = app.getMlDatabase();
}
return mlDatabase;
}
public String getFilter() {
return mEditText.getText().toString().trim();
}
public Integer getAnnotationID(){
if(mAnnotation != null){
return mAnnotation.getDbKey();
}
return -1;
}
private LoaderManager getLoaderManager(){
if(mContentFragment == null){
Log.d(TAG, "ContentFragment is NULL!");
return null;
}
return mContentFragment.getContentActivity().getSupportLoaderManager();
}
private void restartLoader(){
LoaderManager lm = getLoaderManager();
if(lm != null){
lm.restartLoader(TAGLOADERID, null, this);
}
}
private void hideIMM(){
InputMethodManager imm = (InputMethodManager)getContext().getSystemService(Context.INPUT_METHOD_SERVICE);
imm.hideSoftInputFromWindow(mEditText.getWindowToken(), 0);
}
#Override
public Loader<Cursor> onCreateLoader(int id, Bundle args) {
TagCursorLoader loader = new TagCursorLoader(getContext(), this);
return loader;
}
#Override
public void onLoadFinished(Loader<Cursor> cursorLoader, Cursor data) {
if(isNewTagView) {
mSuggestionAdapter.changeCursor(data);
} else {
mTagAdapter.changeCursor(data);
}
mListView.invalidateViews();
mProgressBar.setVisibility(View.GONE);
mListView.setVisibility(View.VISIBLE);
}
#Override
public void onLoaderReset(Loader<Cursor> cursorLoader) {
if(mSuggestionAdapter != null) {
mSuggestionAdapter.changeCursor(null);
}
}
public static class TagCursorLoader extends CursorLoader {
private final ForceLoadContentObserver mObserver = new ForceLoadContentObserver();
private TagDialog dialog;
private MLDatabase mlDatabase;
private Cursor mCursor;
private String mFilter;
private Integer mAnnotationID;
// Runs on worker thread
#Override
public Cursor loadInBackground(){
Cursor cursor = null;
if(dialog.isNewTagView){
mFilter = dialog.getFilter();
cursor = mlDatabase.getTagSuggestions(mFilter);
} else {
mAnnotationID = dialog.getAnnotationID();
}
if(cursor != null){
cursor.registerContentObserver(mObserver);
}
return cursor;
}
//Runs on UI thread
#Override
public void deliverResult(Cursor cursor){
//Handle if canceled in the middle.
if(isReset()){
if(cursor != null){
cursor.close();
}
return;
}
Cursor oldCursor = mCursor;
mCursor = cursor;
if(isStarted()) {
super.deliverResult(cursor);
}
if(oldCursor != null && !oldCursor.equals(cursor) && !oldCursor.isClosed()) {
oldCursor.close();
}
}
public TagCursorLoader(Context context, TagDialog dialog) {
super(context);
this.dialog = dialog;
mlDatabase = dialog.getDatabase();
}
#Override
public void onStartLoading(){
if(mCursor == null) {
forceLoad();
} else {
if(dialog.isNewTagView && mFilter.equals(dialog.getFilter())) {
deliverResult(mCursor);
} else {
forceLoad();
}
}
}
#Override
protected void onStopLoading() {
// Attempt to cancel the current load task if possible.
cancelLoad();
}
#Override
public void onCanceled(Cursor cursor) {
if (cursor != null && !cursor.isClosed()) {
cursor.close();
}
}
#Override
protected void onReset() {
super.onReset();
// Ensure the loader is stopped
onStopLoading();
if (mCursor != null && !mCursor.isClosed()) {
mCursor.close();
}
mCursor = null;
}
}
}
package org.lds.ldssa;
import android.R;
import android.content.Context;
import android.database.Cursor;
import android.view.LayoutInflater;
import android.view.View;
import android.view.ViewGroup;
import android.support.v4.widget.CursorAdapter;
import android.widget.TextView;
import org.lds.ldssa.service.MLDatabase;
public class TagSuggestionAdapter extends CursorAdapter {
public TagSuggestionAdapter(Context context, Cursor cursor, int flags){
super(context, cursor, flags);
}
#Override
public View newView(Context context, Cursor cursor, ViewGroup parent) {
LayoutInflater inflater = LayoutInflater.from(context);
View v = inflater.inflate(R.layout.simple_list_item_1, parent, false);
bindView(v, context, cursor);
return v;
}
#Override
public void bindView(View view, Context context, Cursor cursor) {
TextView tv = (TextView) view.findViewById(R.id.text1);
tv.setText(cursor.getString(cursor.getColumnIndex(MLDatabase.CL_ID)));
}
}
Thanks in Advance for any help.
XML
<?xml version="1.0" encoding="utf-8"?>
<RelativeLayout
xmlns:android="http://schemas.android.com/apk/res/android"
android:id="#+id/tag_layout"
android:orientation="vertical"
android:layout_height="wrap_content"
android:layout_width="#dimen/min_dialog_width"
android:padding="5dp"
android:animateLayoutChanges="true"
>
<!-- Here is the view to show if the list is emtpy -->
<TextView
android:id="#android:id/empty"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:padding="50dp"
android:textAppearance="?android:attr/textAppearanceMedium"
android:layout_centerInParent="true"
android:gravity="center"
android:text="#string/no_items"
android:visibility="invisible"
/>
<ListView
android:id="#android:id/list"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:visibility="invisible"
/>
<ProgressBar
android:id="#+id/tag_spin_progress_bar"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_centerInParent="true"
android:indeterminate="true"
/>
</RelativeLayout>
Try it without
android:animateLayoutChanges="true"
... in your XML layout.