I have a widget View as below:
public class RemoteNumView extends FrameLayout {
how call I use Roboguice just as in RoboActivity? As below:
#InjectView(R.id.btn_remote_control_num_0)
private TextView mText;
Full code is:
/**
* Created by bbcv on 13-12-12.
*/
public class RemoteNumView extends FrameLayout {
private IService mService;
#InjectView(R.id.btn_remote_control_num_0)
private TextView mText;
public RemoteNumView(Context context) {
super(context);
///
addView(LayoutInflater.from(context).inflate(R.layout.v_remote_control_fun,null));
}
public RemoteNumView(Context context, AttributeSet attrs) {
super(context, attrs);
}
public RemoteNumView(Context context, AttributeSet attrs, int defStyle) {
super(context, attrs, defStyle);
}
public void setService(IService service){
mService = service;
}
}
Anyone can HELP?
Solved it by writing custom code. Roboguice is badly written for this purpose.
protected void injectViews() {
for (Field field : this.getClass().getDeclaredFields()) {
if (field.isAnnotationPresent(InjectView.class)) {
if (Modifier.isStatic(field.getModifiers())) {
throw new UnsupportedOperationException("Views can't be staticaly assigned.");
} else {
if (View.class.isAssignableFrom(field.getType())) {
try {
final InjectView injectView = field.getAnnotation(InjectView.class);
;
final int id = injectView.value();
View view = findViewById(id);
if ((view == null) && Nullable.notNullable(field)) {
throw new NullPointerException(String.format("Can't inject null value into %s.%s when field is not #Nullable", field.getDeclaringClass(), field.getName()));
}
field.setAccessible(true);
field.set(this, view);
} catch (IllegalAccessException e) {
throw new IllegalStateException(e);
}
} else {
throw new UnsupportedOperationException("Need view type to assign");
}
}
}
}
Related
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've been using 2-way databinding for a basic application, it was going pretty well, until i start with custom views and attrs.
I want to create a custom view, with has a TextView and a EditText, and use it inside another layout:
<TextView
android:text="Holder"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:id="#+id/tvTitle"
android:layout_weight="1" />
<EditText
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:inputType="none"
android:text="Name"
android:ems="10"
android:id="#+id/etAnwser"
android:layout_weight="1" />
And i have the custom attr for it
<resources>
<declare-styleable name="form_item">
<attr name="tvTitle" format="string" />
<attr name="anwserHint" format="string" />
<attr name="anwserText" format="string" />
<attr name="android:enabled" />
</declare-styleable>
In the fragment i do the following:
<rhcloud.com.financialcontrol.tabutil.FormItem
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:enabled="#{state.get()}"
form_item:anwserText='#={expense.description}'
form_item:tvTitle="Description:" />
It works nice has 1-way databind, but whatever i change the text, he don't send me the callback in class
#InverseBindingMethods(value = {
#InverseBindingMethod(type = FormItem.class, attribute = "anwserText"),
})
public class FormItem extends LinearLayout {
private TextView tvTitle;
private EditText etAnwser;
public FormItem(#NonNull Context context) {
super(context);
LayoutInflater inflater = (LayoutInflater) getContext().getSystemService(Context.LAYOUT_INFLATER_SERVICE);
inflater.inflate(R.layout.form_item, this);
tvTitle = (TextView) findViewById(R.id.tvTitle);
etAnwser = (EditText) findViewById(R.id.etAnwser);
}
public FormItem(#NonNull Context context, #NonNull String title) {
this(context);
setTvTitle(title);
}
public FormItem(#NonNull Context context, #NonNull String title, #NonNull String hint) {
this(context, title);
setAnwserHint(hint);
}
public FormItem(#NonNull Context context, #NonNull String title, #NonNull String hint, #NonNull String anwserText) {
this(context, title, hint);
setAnwserHint(anwserText);
}
public FormItem(#NonNull Context context, #NonNull AttributeSet attrs) {
super(context, attrs);
LayoutInflater inflater = (LayoutInflater) getContext().getSystemService(Context.LAYOUT_INFLATER_SERVICE);
inflater.inflate(R.layout.form_item, this);
tvTitle = (TextView) findViewById(R.id.tvTitle);
etAnwser = (EditText) findViewById(R.id.etAnwser);
TypedArray a = context.getTheme().obtainStyledAttributes(
attrs,
R.styleable.form_item,
0, 0);
try {
setTvTitle(a.getString(R.styleable.form_item_tvTitle));
setAnwserHint(a.getString(R.styleable.form_item_anwserHint));
setAnwserText(a.getString(R.styleable.form_item_anwserText));
String isEnabled = a.getString(R.styleable.form_item_android_enabled);
if (isEnabled != null) {
setEnable(Boolean.parseBoolean(isEnabled));
}
} finally {
a.recycle();
}
}
public void setTvTitle(String title) {
tvTitle.setText(title);
}
public String getTvTitle() {
return tvTitle.getText().toString();
}
public void setAnwserHint(String hint) {
etAnwser.setHint(hint);
}
public String getAnwserHint() {
return etAnwser.getHint().toString();
}
public void setEnable(boolean isEnable) {
tvTitle.setEnabled(isEnable);
etAnwser.setEnabled(isEnable);
}
public void setAnwserText(String anwserText) {
etAnwser.setText(anwserText);
}
public String getAnwserText() {
return etAnwser.getText().toString();
}
#InverseBindingAdapter(attribute = "form_item:anwserText")
public static String setOnAnwserTextAttrChanged(final String value){
Log.d("Test","Calling InverseBindingAdapter: " + value);
return value;
}
#BindingAdapter(value = {"anwserTextAttrChanged"},
requireAll = false)
public static void setOnAnwserTextAttrChanged(final FormItem view,final InverseBindingListener anwserTextAttrChanged){
Log.d("Test","Calling BindingAdapter: " + view.getAnwserText());
if(anwserTextAttrChanged == null){
}else{
Log.d("Test","Calling here");
anwserTextAttrChanged.onChange();
}
}
#BindingAdapter(value = {"android:enabled"})
public static void customEnable(FormItem formItem, boolean isEnable) {
formItem.setEnable(isEnable);
}
}
Does anyone know how to make it work properly?
Fully code can be found at here
This works for me:
#InverseBindingMethods(value = {
#InverseBindingMethod(type = FilterPositionView.class, attribute = "bind:filterStringValue", method = "getFilterValue", event = "android:filterStringValuetAttrChanged")
})
public class FilterPositionView extends LinearLayout {
private FilterPositionBinding mBinding;
public FilterPositionView(Context context) {
super(context);
init(context);
}
public FilterPositionView(Context context, AttributeSet attrs) {
super(context, attrs);
init(context);
}
public FilterPositionView(Context context, AttributeSet attrs, int defStyleAttr) {
super(context, attrs, defStyleAttr);
init(context);
}
public FilterPositionView(Context context, AttributeSet attrs, int defStyleAttr, int defStyleRes) {
super(context, attrs, defStyleAttr, defStyleRes);
init(context);
}
private void init(Context context) {
mBinding = DataBindingUtil.inflate(LayoutInflater.from(context), R.layout.filter_position, this, true);
setOrientation(HORIZONTAL);
mBinding.filterPositionCheck.setOnCheckedChangeListener((buttonView, isChecked) -> {
mBinding.filterPositionValue.setEnabled(isChecked);
if (!isChecked) mBinding.filterPositionValue.setText("");
});
}
/**
* Zwraca wpisywany text
*
* #return wpisane litery tekstu
*/
public String getFilterValue() {
return mBinding.filterPositionValue.getText().toString();
}
#BindingAdapter(value = {"bind:filterTitle", "bind:filterStringValue", "bind:filterDateValue"}, requireAll = false)
public static void setFilterBinding(FilterPositionView positionView, String filterTitle,
String filterStringValue, Long filterDateValue) {
positionView.mBinding.filterPositionTitle.setText(filterTitle);
if (filterStringValue != null)
positionView.mBinding.filterPositionValue.setText(filterStringValue);
if (filterDateValue != null)
positionView.mBinding.filterPositionValue.setText(DateTimeFormatUtil.format(filterDateValue));
}
#BindingAdapter(value = {"android:afterTextChanged", "android:filterStringValuetAttrChanged"}, requireAll = false)
public static void setTextWatcher(FilterPositionView filterPositionView, final TextViewBindingAdapter.AfterTextChanged after,
final InverseBindingListener textAttrChanged) {
TextWatcher newValue = 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) {
if (after != null) {
after.afterTextChanged(s);
}
if (textAttrChanged != null) {
textAttrChanged.onChange();
}
}
};
TextWatcher oldValue = ListenerUtil.trackListener(filterPositionView.mBinding.filterPositionValue, newValue, R.id.textWatcher);
if (oldValue != null) {
filterPositionView.mBinding.filterPositionValue.removeTextChangedListener(oldValue);
}
filterPositionView.mBinding.filterPositionValue.addTextChangedListener(newValue);
}
}
Of course You have to add #={} in your XML layouts like below:
<com.example.customviews.FilterPositionView
style="#style/verticalLabeledValueStyle"
android:layout_width="match_parent"
android:layout_height="wrap_content"
bind:filterTitle="#{#string/filter_product}"
bind:filterStringValue="#={sfmodel.product}"/>
Hello I am new programmer and I have a custom view in my app that i try to make to redraw itself after i give him new data i tried using invalidate but it seem to not work I'm not sure if the problem is the way i update the data or the way i try to make the view redraw itself. any help will be appreciated.
view:
public class BlackJackView extends View {
private PlayerView playerView, computerView;
private BlackJackGame game;
private Context contex;
public void setPlayer(BlackJackPlayer player) {
playerView = new PlayerView(contex, player);
}
public void setComputer(BlackJackPlayer computer) {
computerView = new PlayerView(contex, computer);
}
public BlackJackView(Context context) {
super(context);
init(context);
}
public BlackJackView(Context context, AttributeSet attrs) {
super(context, attrs);
init(context);
}
public BlackJackView(Context context, AttributeSet attrs, int defStyle) {
super(context, attrs, defStyle);
init(context);
}
private void init(Context context) {
this.contex = context;
game = new BlackJackGame();
playerView = new PlayerView(contex, game.getPlayer());
computerView = new PlayerView(contex, game.getComputer());
}
public void init(BlackJackGame game) {
this.game = new BlackJackGame(game.getPlayer().getName(),game.getPlayer().getMoney());
playerView = new PlayerView(contex, game.getPlayer());
computerView = new PlayerView(contex, game.getComputer());
this.invalidate();
}
#Override
public void onDraw(Canvas canvas) {
playerView.onDraw(canvas);
computerView.onDraw(canvas);
}
}
activity:
public class BlackJackActivity extends Activity {
private UsersDataSource dataSource;
private List<User> values;
private BlackJackView view;
private BlackJackGame game;
private int userID;
private int playerMoney;
private String playerName;
#Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
game = new BlackJackGame();
view = new BlackJackView(this);
view.setPlayer(game.getPlayer());
setContentView(R.layout.black_jack_activity);
GetId(savedInstanceState);
GetPlayerData();
view = (BlackJackView) findViewById(R.id.blackJackView);
view.init(game);
}
private void GetId(Bundle savedInstanceState) {
if (savedInstanceState == null) {
Bundle extras = getIntent().getExtras();
if (extras == null) {
userID = -1;
} else {
userID = extras.getInt("ID");
}
} else {
userID = (int) savedInstanceState.getSerializable("ID");
}
}
private void GetPlayerData() {
//data base open
dataSource = new UsersDataSource(this);
dataSource.open();
//get list of database
values = dataSource.getAllUsers();
playerName = values.get(userID).getUsername();
playerMoney = (int) values.get(userID).getTokens();
}
#Override
protected void onResume() {
dataSource.open();
super.onResume();
}
#Override
protected void onPause() {
dataSource.close();
super.onPause();
}
}
In my android app I have a list of user's items.
Using a custom adapter to display them, overriding the GetView method.
From a book I got the WebImageView to lazy load images and customized it a bit.
The problem is that when I open the list view and scroll up and down, images get mixed up constantly
Here is some code:
#Override
public View getView(int position, View convertView, ViewGroup parent) {
if (convertView == null) {
convertView = layoutInflater.inflate(R.layout.item_adapterable_my_profile_item, parent, false);
}
iMyItemsFeedItemImage = (ImageWebView) convertView.findViewById(R.id.iMyItemsFeedItemImage);
tvMyItemsFeedItemName = (TextView) convertView.findViewById(R.id.tvMyItemsFeedItemName);
tvMyItemsFeedItemName.setText(itemNames.get(position));
iMyItemsFeedItemImage.setPlaceholderImage(R.drawable.images_default_product);
iMyItemsFeedItemImage.setVisibility(View.VISIBLE);
iMyItemsFeedItemImage.setImageUrl(C.API.WEB_ADDRESS + C.API.IMAGES_ITEMS_FOLDER_THUMBNAIL + itemImages.get(position));
return convertView;
} // End of getView
and the ImageWebView class:
public class ImageWebView extends ImageView implements OnDownloadImageListener {
private Drawable mPlaceholder;
private Drawable mImage;
private Bitmap cachedBitmap;
private boolean imageBitmapCached = false;
public ImageWebView(Context context) {
this(context, null);
}
public ImageWebView(Context context, AttributeSet attrs) {
this(context, attrs, 0);
}
public ImageWebView(Context context, AttributeSet attrs, int defaultStyle) {
super(context, attrs, defaultStyle);
}
public void setPlaceholderImage(Drawable drawable) {
mPlaceholder = drawable;
if (mImage == null) {
setImageDrawable(mPlaceholder);
}
}
public void setPlaceholderImage(int resid) {
mPlaceholder = getResources().getDrawable(resid);
if (mImage == null) {
setImageDrawable(mPlaceholder);
}
}
public void setImageUrl(String url) {
if (imageBitmapCached) {
setImageBitmap(cachedBitmap);
} else {
new DownloadImage(this, url).execute();
}
}
#Override
public void onDownloadImageSuccess(Bitmap image) {
setImageBitmap(image);
cachedBitmap = image;
imageBitmapCached = true;
}
#Override
public void onDownloadImageFailure() {
};
} // End of Class
The names remain the same, in the same order that they've been initially, but the images get mixed up
The ListView is recycling views, which means that once you scroll down, the download you triggered for a list item might not apply anymore, because that same list item view has been used to display an item at the bottom of the list, which should have a different image.
What you need to do, is set the URL of the image as a tag to your ImageWebView in your setImageUrl method, and then in onImageDownloaded, check if the Url in the tag is still the same as the one you just downloaded. If it's not, it means that your ImageWebView is already being used for a new list item, and you shouldn't set the image. For that you should also add the downloaded image Url as a parameter to your onImageDownloaded method. So the complete solution is:
public void setImageUrl(String url) {
setTag(url);
if (imageBitmapCached) {
setImageBitmap(cachedBitmap);
} else {
new DownloadImage(this, url).execute();
}
}
#Override
public void onDownloadImageSuccess(Bitmap image, String url) {
if(url.equals.((String) getTag())){
setImageBitmap(image);
cachedBitmap = image;
imageBitmapCached = true;
}
}
EDIT:
I would change your entire ImageWebView class like this:
public class ImageWebView extends ImageView implements OnDownloadImageListener {
public ImageWebView(Context context) {
this(context, null);
}
public ImageWebView(Context context, AttributeSet attrs) {
this(context, attrs, 0);
}
public ImageWebView(Context context, AttributeSet attrs, int defaultStyle) {
super(context, attrs, defaultStyle);
}
public void setImageUrl(String url, int placeholderResId) {
String oldUrl = (String) getTag();
setTag(url);
if (!url.equals(oldUrl)) {
setImageResource(placeholderResId);
new DownloadImage(this, url).execute();
}
}
#Override
public void onDownloadImageSuccess(Bitmap image, String url) {
if(url.equals((String) getTag())){
setImageBitmap(image);
}
}
And in your adapter, just don't call setPlaceholderImage, simply call the new version of setImageUrl. with the placeholder resource id:
iMyItemsFeedItemImage.setImageUrl(C.API.WEB_ADDRESS + C.API.IMAGES_ITEMS_FOLDER_THUMBNAIL + itemImages.get(position), R.drawable.images_default_product);
You should use shutterbug library to to display images from Url. Its easy n effective.
I'm currently programming in Monodroid and I'm having an issue with the extension of a Listview.
I currently have ListView extended like this:
public class TTListView : ListView
{
private Context mContext;
private bool wrapAdapter;
public TTListView(Context context) :
base(context)
{
Initialize();
this.mContext = context;
}
public TTListView(Context context, IAttributeSet attrs) :
base(context, attrs)
{
Initialize();
this.mContext = context;
}
public TTListView(Context context, IAttributeSet attrs, int defStyle) :
base(context, attrs, defStyle)
{
Initialize();
this.mContext = context;
}
private void Initialize()
{
this.CacheColorHint = Color.Transparent;
//Still some more stuff to be added here
}
public void InsertItemAt(int index)
{
Animation anim = AnimationUtils.LoadAnimation(
mContext, Resource.Animator.slide_top_down);
anim.Duration = 500;
this.GetChildAt(index).StartAnimation(anim);
}
public void SetDelegate(TTListDelegate _delegate)
{
this.OnItemClickListener = (IOnItemClickListener)_delegate;
this.OnItemLongClickListener = (IOnItemLongClickListener)_delegate;
}
public override void AddFooterView(View v)
{
base.AddFooterView(v);
wrapAdapter = true;
}
/*public override IListAdapter Adapter
{
get
{
return base.Adapter;
}
set
{
//Check if the passed parameter is a TTListAdapter
TTListAdapter _ttadapter = value as TTListAdapter;
if (_ttadapter != null)
{
_ttadapter.Wrapped = wrapAdapter;
}
base.Adapter = value;
}
}*/
}
The above code works perfectly fine.
The problem with this is when I'm trying to override the Adapter property (which is now commented out) I get the following exception when trying to create a TTListView object:
"Unable to activate instance of type TimeTellApp.TTListView from native handle 40557188. No constructor found for TTListView::.ctor(System.IntPtr, Android.Runtime.JniHandleOwner)"
Usually this has something to do with the GC destroying the managed mapped object so up until now I solved these kind of problems by keeping a reference to the object. The problem with the TTListView is that the exception already comes up when calling the constructor for initialization.
I create a TTListView object like this:
TTListView setting_listview = new TTListView(this);
(Where this is a Activity)
What could be the problem here and what would be the best way to solve it?
The error message says that you're missing a specific constructor in your class, which you should implement:
protected ListView (IntPtr javaReference, Android.Runtime.JniHandleOwnership transfer)