This is my code: here some edit texts are there.
public class MainActivity extends ActionBarActivity {
#InjectView(R.id.edt_fname) protected EditText account_fname;
#InjectView(R.id.edt_lnames) protected EditText account_lname;
#InjectView(R.id.edt_userid) protected EditText account_userid;
#InjectView(R.id.edt_pwd) protected EditText account_password;
#InjectView(R.id.edt_reenter) protected EditText account_reenter_pswd;
#InjectView(R.id.nxt_btn1) protected Button next_acct;
#Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
ButterKnife.inject(this);
next_acct.setEnabled(false);
if(( !account_fname.getText().toString().equals("")) &&
( !account_lname.getText().toString().equals("")) &&
( !account_userid.getText().toString().equals("")) &&
( !account_password.getText().toString().equals("")) &&
( !account_reenter_pswd.getText().toString().equals("")) )
{
next_acct.setEnabled(true);
next_acct.setOnClickListener(new View.OnClickListener() {
#Override
public void onClick(View view) {
startActivity(new Intent(getApplicationContext(), PersonalInfo.class));
overridePendingTransition(R.anim.slide_in_right, R.anim.slide_out_left);
}
});
} }
My aim is that when the fields are empty, then the submit button is disabled.
If all fields are filled with some text, then the submit button is enabled.
How can I do this?
this is very simple...
if(edttext.getText().toString().equals(edttext2.getText().toString()))
{
submit_button.setEnabled(true);
}
else
{
submit_button.setEnabled(true);
}
try this
if((account_fname.getText() != null ) &&
( account_lname.getText() != null) &&
( account_userid.getText() != null) &&
( account_password.getText() != null) &&
( account_reenter_pswd.getText() != null) )
{
next_acct.setEnabled(true);
}
You're doing it inside the onCreate method. You need to add a textChangeListener on the EditText then place your if statement in the textChangeListener or create a method then put the if statement in the method then call the method in the TextChangeListener.
For reference on TextChangeListener: Counting Chars in EditText Changed Listener
The challenge here is really in gathering the content of the EditText fields. That is, once you have a simple call to retrieve all interesting TextView or EditText fields, then the problem becomes trivial.
I tested this, seems to work. I believe it's less fragile to changes in the structure or nesting of the EditText fields of the layout.
To answer the question in the topic, the submit button can be enabled if the array contains no "null" entries from the result of the "gatherEditTextContent(View root)" method.
public static String[] gatherEditTextContent(View root) {
final Vector<String> v = new Vector();
Boolean stop = false;
scanEditText(root, new ETCallback() {
#Override
public boolean onEditText(EditText editText) {
v.add((editText.getText() == null ? "null" : editText.getText().toString()));
return false;
}
});
return v.toArray(new String[v.size()]);
}
/** initiate by passing v= root, callback non-null */
public static boolean scanEditText(View v, ETCallback callback) {
boolean stop = false;
if (v instanceof EditText) {
if (callback != null) stop = callback.onEditText((EditText)v);
}
else if (v instanceof ViewGroup) {
ViewGroup vg = (ViewGroup) v;
for (int i=0; (!stop && i < vg.getChildCount()); i++) {
View child = vg.getChildAt(i);
stop = scanEditText(child, callback);
}
}
return stop;
}
private interface ETCallback {
public boolean onEditText(EditText editText);
}
Related
I'm playing with a drawing activity in Java converted/decompiled from this Kotlin sample.
I'm simplifying its functionalities and, as it is now, it allows me to click on "Save" button and a preview pops up with a text saying "Saved!", but I'd like to know what needs to be done to simply throw the resulting image to the Android photo gallery anytime the button is clicked (let's say, after saved, the image must become a standalone picture inside the camera gallery).
It seems it has to do with FileOutputStream/Bitmap.CompressFormat/MediaStore.Images
and I can foresee some difficulties in terms of naming files in a way they don't overwrite and I'm reading a lot of answers around here, but I still didn't get the logic so any idea is appreciated.
It's the first time I'm trying to do something similar so I'm sort of lost and I come here to ask for some directions.
Here is the single activity:
public final class SampleActivity extends AppCompatActivity implements OnSeekBarChangeListener, OnClickListener {
private HashMap _$_findViewCache;
protected void onCreate(#Nullable Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
this.setContentView(R.layout.activity_sample);
(this._$_findCachedViewById(id.close)).setOnClickListener(this);
(this._$_findCachedViewById(id.save)).setOnClickListener(this);
(this._$_findCachedViewById(id.undo)).setOnClickListener(this);
(this._$_findCachedViewById(id.clear)).setOnClickListener(this);
((SeekBar)this._$_findCachedViewById(id.red)).setOnSeekBarChangeListener(this);
((SeekBar)this._$_findCachedViewById(id.green)).setOnSeekBarChangeListener(this);
((SeekBar)this._$_findCachedViewById(id.blue)).setOnSeekBarChangeListener(this);
((SeekBar)this._$_findCachedViewById(id.width)).setOnSeekBarChangeListener(this);
}
public void onProgressChanged(#Nullable SeekBar seekBar, int progress, boolean fromUser) {
int var10000;
SeekBar var10001;
label58: {
label50: {
if (seekBar != null) {
var10000 = seekBar.getId();
var10001 = (SeekBar)this._$_findCachedViewById(id.red);
Intrinsics.checkExpressionValueIsNotNull(var10001, "red");
if (var10000 == var10001.getId()) {
break label50;
}
}
if (seekBar != null) {
var10000 = seekBar.getId();
var10001 = (SeekBar)this._$_findCachedViewById(id.green);
Intrinsics.checkExpressionValueIsNotNull(var10001, "green");
if (var10000 == var10001.getId()) {
break label50;
}
}
if (seekBar == null) {
break label58;
}
var10000 = seekBar.getId();
var10001 = (SeekBar)this._$_findCachedViewById(id.blue);
Intrinsics.checkExpressionValueIsNotNull(var10001, "blue");
if (var10000 != var10001.getId()) {
break label58;
}
}
SeekBar var8 = (SeekBar)this._$_findCachedViewById(id.red);
Intrinsics.checkExpressionValueIsNotNull(var8, "red");
int r = var8.getProgress();
var8 = (SeekBar)this._$_findCachedViewById(id.green);
Intrinsics.checkExpressionValueIsNotNull(var8, "green");
int g = var8.getProgress();
var8 = (SeekBar)this._$_findCachedViewById(id.blue);
Intrinsics.checkExpressionValueIsNotNull(var8, "blue");
int b = var8.getProgress();
int color = Color.argb(255, r, g, b);
((FingerPaintImageView)this._$_findCachedViewById(id.finger)).setStrokeColor(color);
(this._$_findCachedViewById(id.colorPreview)).setBackgroundColor(color);
return;
}
if (seekBar != null) {
var10000 = seekBar.getId();
var10001 = (SeekBar)this._$_findCachedViewById(id.width);
Intrinsics.checkExpressionValueIsNotNull(var10001, "width");
if (var10000 == var10001.getId()) {
((FingerPaintImageView)this._$_findCachedViewById(id.finger)).setStrokeWidth((float)progress);
}
}
}
public void onClick(#Nullable View v) {
if (Intrinsics.areEqual(v, this._$_findCachedViewById(id.undo))) {
((FingerPaintImageView)this._$_findCachedViewById(id.finger)).undo();
} else if (Intrinsics.areEqual(v, this._$_findCachedViewById(id.clear))) {
((FingerPaintImageView)this._$_findCachedViewById(id.finger)).clear();
} else if (Intrinsics.areEqual(v, this._$_findCachedViewById(id.close))) {
this.hidePreview();
} else if (Intrinsics.areEqual(v, this._$_findCachedViewById(id.save))) {
this.showPreview();
}
}
private final void showPreview() {
RelativeLayout var10000 = (RelativeLayout)this._$_findCachedViewById(id.previewContainer);
Intrinsics.checkExpressionValueIsNotNull(var10000, "previewContainer");
var10000.setVisibility(View.VISIBLE);
ImageView var1 = (ImageView)this._$_findCachedViewById(id.preview);
FingerPaintImageView var10001 = (FingerPaintImageView)this._$_findCachedViewById(id.finger);
Intrinsics.checkExpressionValueIsNotNull(var10001, "finger");
var1.setImageDrawable(var10001.getDrawable());
}
private final void hidePreview() {
RelativeLayout var10000 = (RelativeLayout)this._$_findCachedViewById(id.previewContainer);
Intrinsics.checkExpressionValueIsNotNull(var10000, "previewContainer");
var10000.setVisibility(View.GONE);
}
public void onStartTrackingTouch(#Nullable SeekBar seekBar) {
}
public void onStopTrackingTouch(#Nullable SeekBar seekBar) {
}
public void onBackPressed() {
RelativeLayout var10000 = (RelativeLayout)this._$_findCachedViewById(id.previewContainer);
Intrinsics.checkExpressionValueIsNotNull(var10000, "previewContainer");
if (var10000.getVisibility() == View.VISIBLE) {
this.hidePreview();
} else {
super.onBackPressed();
}
}
public View _$_findCachedViewById(int var1) {
if (this._$_findViewCache == null) {
this._$_findViewCache = new HashMap();
}
View var2 = (View)this._$_findViewCache.get(var1);
if (var2 == null) {
var2 = this.findViewById(var1);
this._$_findViewCache.put(var1, var2);
}
return var2;
}
public void _$_clearFindViewByIdCache() {
if (this._$_findViewCache != null) {
this._$_findViewCache.clear();
}
}
}
Thanks in advance!
I was able to overcome this issue by taking another paint-like sample (a simpler one and in Java) called Android Drawable View.
This different sample and tips from previous answers available here on StackOverflow like this one and this other one were enough to put the project together so I'll try to explain how to.
First, you need to add permission to WRITE_EXTERNAL_STORAGE in your Manifest.xml:
<uses-permission android:name="android.permission.WRITE_EXTERNAL_STORAGE" />
Later, you just need to add a save button to your activity_main.xml:
<Button
android:id="#+id/saveButton"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="Save" />
Then, you initialize the button view onCreate and associate the new saveButton with a setOnClickListener and don't forget to request permission in realtime:
Button saveButton = findViewById(R.id.saveButton);
saveButton.setOnClickListener(new View.OnClickListener() {
#Override public void onClick(View v) {
if (ContextCompat.checkSelfPermission(getBaseContext(), Manifest.permission.CAMERA) ==
PackageManager.PERMISSION_GRANTED) {
drawableView.setEnabled(true);
}
else {
ActivityCompat.requestPermissions(MainActivity.this, new String[]
{ Manifest.permission.CAMERA, Manifest.permission.WRITE_EXTERNAL_STORAGE }, 0);
}
Bitmap bm = drawableView.obtainBitmap();
MediaStore.Images.Media.insertImage(getContentResolver(), bm, "title" , "description");
}
});
By using the method described above, I've been able to save a new media file inside a folder in the default gallery app on the emulator as you can see below:
However, it's still getting an unintended black background that I must overcome now, but I consider the initial issue solved as it answers my own original question.
Any idea how to illustrate backspace funtion in this code? I try to make some changes but it can't work the backspace function. So, i would like to help me, with the backspace button.
enter code here
public class MainActivity extends AppCompatActivity implements OnClickListener {
private TextView mCalculatorDisplay;
private Boolean userIsInTheMiddleOfTypingANumber = false;
private CalculatorBrain mCalculatorBrain;
private static final String DIGITS = "0123456789.";
DecimalFormat df = new DecimalFormat("############");
#SuppressLint("NewApi")
#Override
protected void onCreate(Bundle savedInstanceState) {
// hide the window title.
requestWindowFeature(Window.FEATURE_NO_TITLE);
// hide the status bar and other OS-level chrome
getWindow().addFlags(WindowManager.LayoutParams.FLAG_FULLSCREEN);
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
mCalculatorBrain = new CalculatorBrain();
mCalculatorDisplay = (TextView) findViewById(R.id.textView1);
df.setMinimumFractionDigits(0);
df.setMinimumIntegerDigits(1);
df.setMaximumIntegerDigits(8);
findViewById(R.id.button0).setOnClickListener(this);
findViewById(R.id.button1).setOnClickListener(this);
findViewById(R.id.button2).setOnClickListener(this);
findViewById(R.id.button3).setOnClickListener(this);
findViewById(R.id.button4).setOnClickListener(this);
findViewById(R.id.button5).setOnClickListener(this);
findViewById(R.id.button6).setOnClickListener(this);
findViewById(R.id.button7).setOnClickListener(this);
findViewById(R.id.button8).setOnClickListener(this);
findViewById(R.id.button9).setOnClickListener(this);
findViewById(R.id.buttonBackspace).setOnClickListener(this);
findViewById(R.id.buttonAdd).setOnClickListener(this);
findViewById(R.id.buttonSubtract).setOnClickListener(this);
findViewById(R.id.buttonMultiply).setOnClickListener(this);
findViewById(R.id.buttonDivide).setOnClickListener(this);
findViewById(R.id.buttonToggleSign).setOnClickListener(this);
findViewById(R.id.buttonDecimalPoint).setOnClickListener(this);
findViewById(R.id.buttonEquals).setOnClickListener(this);
findViewById(R.id.buttonClear).setOnClickListener(this);
// The following buttons only exist in layout-land (Landscape mode) and require extra attention.
// The messier option is to place the buttons in the regular layout too and set android:visibility="invisible".
if (findViewById(R.id.buttonSquareRoot) != null) {
findViewById(R.id.buttonSquareRoot).setOnClickListener(this);
}
if (findViewById(R.id.buttonSquared) != null) {
findViewById(R.id.buttonSquared).setOnClickListener(this);
}
if (findViewById(R.id.buttonInvert) != null) {
findViewById(R.id.buttonInvert).setOnClickListener(this);
}
if (findViewById(R.id.buttonSine) != null) {
findViewById(R.id.buttonSine).setOnClickListener(this);
}
if (findViewById(R.id.buttonCosine) != null) {
findViewById(R.id.buttonCosine).setOnClickListener(this);
}
if (findViewById(R.id.buttonTangent) != null) {
findViewById(R.id.buttonTangent).setOnClickListener(this);
}
}
#Override
public void onClick (View v) {
String buttonPressed = ((Button) v).getText().toString();
if (DIGITS.contains(buttonPressed)) {
// digit was pressed
if (userIsInTheMiddleOfTypingANumber) {
if (buttonPressed.equals(".") && mCalculatorDisplay.getText().toString().contains(".")) {
// ERROR PREVENTION
// Eliminate entering multiple decimals
} else {
mCalculatorDisplay.append(buttonPressed);
}
} else {
if (buttonPressed.equals(".")) {
// ERROR PREVENTION
// This will avoid error if only the decimal is hit before an operator, by placing a leading zero
// before the decimal
mCalculatorDisplay.setText(0 + buttonPressed);
} else {
mCalculatorDisplay.setText(buttonPressed);
}
}
userIsInTheMiddleOfTypingANumber = true;
}else{
// operation was pressed
if (userIsInTheMiddleOfTypingANumber) {
mCalculatorBrain.setOperand(Double.parseDouble(mCalculatorDisplay.getText().toString()));
userIsInTheMiddleOfTypingANumber = false;
}
mCalculatorBrain.performOperation(buttonPressed);
if (new Double(mCalculatorBrain.getResult()).equals(0.0)) {
mCalculatorDisplay.setText("" + 0);
} else {
mCalculatorDisplay.setText(df.format(mCalculatorBrain.getResult()));
}
}
}
#Override
protected void onSaveInstanceState(Bundle outState) {
super.onSaveInstanceState(outState);
// Save variables on screen orientation change
outState.putDouble("OPERAND", mCalculatorBrain.getResult());
outState.putDouble("MEMORY", mCalculatorBrain.getMemory());
}
#Override
protected void onRestoreInstanceState(Bundle savedInstanceState){
super.onRestoreInstanceState(savedInstanceState);
// Restore variables on screen orientation change
mCalculatorBrain.setOperand(savedInstanceState.getDouble("OPERAND"));
mCalculatorBrain.setMemory(savedInstanceState.getDouble("MEMORY"));
if (new Double(mCalculatorBrain.getResult()).equals(0.0)){
mCalculatorDisplay.setText("" + 0);
} else {
mCalculatorDisplay.setText(df.format(mCalculatorBrain.getResult()));
}
}
}
In your layout you can add a onClick attribute to each button, say onClick="function", and in your activity you just need to implement a method like this:
public void function(View v) {
switch(v.getId()) {
case R.id.buttonBackspace:
// handle the backspace button
break;
case R.id.xxx:
// handle the button
break;
...
}
}
And for digits, I suggest assign a tag to each digit button in the layout, and do your logic in java based on the tag, instead of the text on the button. Because the text is just a UI, it might change in the future due to other possible requirements.
i am creating my custom EditText which validate email and other things on focus lost and if it is not valid then focus back. It works fine if i have only one EditText but when i have multiple EditText it infinity focuses between my editText boxes as it try to check validation in both. Here is my sample code.
public void init(){
this.setOnFocusChangeListener(new OnFocusChangeListener() {
#Override
public void onFocusChange(View v, boolean hasFocus) {
// TODO Auto-generated method stub
final MyEditText ed = MyEditText.this;
//...............check require field validation
if(!hasFocus && isRequire){
if(ed.getText().toString().length()<=0){
String msg = "Require Field";
v.clearFocus();
setErrorMsg(ed,msg);
return;
}
}else if(ed.getText().toString().length()>0){
ed.setError(null);
}
}
}
private void setErrorMsg(final EditText ed,String msg){
if(errorMessage!=null && errorMessage.length()>0){
msg = errorMessage;
}
ed.setError(msg);
ed.post(new Runnable() {
public void run() {
ed.requestFocus();
}
});
}
Put onfocus change lisner on the parent view from the viewGroup get the child view and chek it u will get the perticular edittext. i had some requirement like that i solved it this way.
TableLayout tableView = (TableLayout)findViewById(R.id.mydetails_tableview);
View mytempView=null;
int noOfChilds=tableView.getChildCount();
for(int i=0;i<noOfChilds;i++)
{
mytempView=tableView.getChildAt(i);
if(i%2==0)
{
View vv=((TableRow) mytempView).getChildAt(1);
if(vv instanceof EditText)
{
//Log.v("This one is edit text---", "here there");
((EditText) vv).setText("");
}
}
}
boolean pendingFocus = false;
Before
ed.post(new Runnable() {
public void run() {
ed.requestFocus();
}
});
Add
pendingFocus = true;
and Replace:
if(!hasFocus && isRequire){
with
if(!hasFocus && isRequire && !pendingFocus){
Finally reset pendingFocus with a new else statement here:
if(ed.getText().toString().length()<=0){
String msg = "Require Field";
v.clearFocus();
setErrorMsg(ed,msg);
return;
}
}else if(ed.getText().toString().length()>0){
ed.setError(null);
}else{
pedingFocus = false;
}
I generate multiple EditText in a for-loop and want to update a "score" when focus is lost from one of the EditTexts. I also wish to check that the input is valid and if not, empty the EditText and set focus back to it.
The following code adds EditTexts and a onFocusChangeListener.
The problem is that when a score is not valid (updateScore(id) returns false), I want to empty the EditText and set the focus back to this view.
The problem in my code is that if I enter a value in EditText A that are not valid, and then click in EditText B. Both A and B have focus... I only want A to have focus...
How can I set the focus back to the previous EditText and be ready for new inputs to this view?
for (int player = 0; player < numPlayers; player++)
{
EditText valueET = new EditText(this);
valueET.setOnClickListener(new OnClickListener() {
#Override public void onClick(View v) {
Log.v("Focus", "Lagrer unna den som har blitt klikket, " + String.valueOf(v.getId()));
etHasFocus = (EditText) v;
}
});
valueET.setOnFocusChangeListener(new OnFocusChangeListener() {
public void onFocusChange(View v, boolean hasFocus) {
if(!hasFocus)
{
if(!updateScore(v.getId()))
{
if (etHasFocus != null && v.getId() != etHasFocus.getId())
etHasFocus.clearFocus();
v.requestFocus();
}
});
valueET.setId(200+player*100+row);
valueET.setInputType(InputType.TYPE_CLASS_NUMBER);
valueET.setTextColor(Color.BLACK);
valueET.setLayoutParams(new LayoutParams(LayoutParams.MATCH_PARENT, LayoutParams.WRAP_CONTENT));
tr.addView(valueET);
}
tl.addView(tr, new TableLayout.LayoutParams( LayoutParams.MATCH_PARENT, LayoutParams.WRAP_CONTENT));
The code for updateScore(id) is (simplified):
boolean updateScore(int id)
{
EditText t = (EditText) findViewById(id);
String text = t.getText().toString();
if( !text.equals(""))
{
int score = Integer.parseInt(text);
}
if(score != 9)
return false;
TextView total = (TextView) findViewById(ID_toal);
t2.setText(String.valueOf(score));
return true;
}
Code is updated, problem now is that onClick never is called... (So etHasFocus = null)
You can try keeping a track of which EditText has been clicked. For this, create a EditText variable:
EditText etHasFocus;
In addition to adding OnFocusChangeListener, add an OnClickListener as well:
valueET.setOnClickListener(new OnClickListener() {
#Override
public void onClick(View v) {
etHasFocus = (EditText) v;
}
});
Now, inside your OnFocusChangeListener, make the following changes:
valueET.setOnFocusChangeListener(new OnFocusChangeListener() {
#Override
public void onFocusChange(View v, boolean hasFocus) {
if(!hasFocus) {
if(!updateScore(v.getId())) {
// Clear focus from another EditText
if (etHasFocus != null && v.getId() != etHasFocus.getId()) {
etHasFocus.clearFocus();
}
v.requestFocus();
}
}
}
});
Edit:
You are right. the onClick(View) method is called on second click. I can suggest you an alternate approach. I have tried it and its working fine:
Create a EditText variable:
EditText etCurrent;
Set an OnTouchlistener on each EditText:
valueET.setOnTouchListener(new OnTouchListener() {
#Override
public boolean onTouch(View v, MotionEvent event) {
if (event.getAction() == MotionEvent.ACTION_DOWN) {
if (etCurrent != null) {
if (etCurrent.getId() != v.getId()) {
if (!check(etCurrent.getId())) {
etCurrent.setSelection(0,
etCurrent.getText().toString().length());
return true;
} else {
etCurrent = (EditText) v;
}
}
} else {
etCurrent = (EditText) v;
}
}
return false;
}
});
Remove OnClickListener, onFocusChangeListener and etHasFocus.
You don't need to initialize etCurrent as if (etCurrent != null) { } else { } takes care of that.
I am making a date picker activity that looks like a scrolling 30 day month/calendar (think Outlook calendar). The date picker contains a ListView (for scrolling) of MonthView views each of which is a TableView of the individual days. Each individual day in the MonthView is a button. When the MonthView is instantiated I walk each of the days (buttons) and attach a click listener:
final Button b = getButtonAt(i);
b.setOnClickListener(new OnClickListener()
{
#Override
public void onClick(View v) {
setSelectedDate(buttonDayClosure, b);
}
});
setSelectedDate does a variety of things, but it also turns the button's background to yellow to signify the date is selected.
On my emulator, everything works as you would expect. Activity comes up, you press a day, the day turns yellow. No problems.
However, on some of my peer's emulators and on physical devices when you touch a day nothing happens... until you scroll the ListView... and then all of a sudden the selected day turns yellow. So, for example, you touch "the 3rd" and then nothing happens. Wait a few seconds and then scroll the ListView (touching an area of the calendar that is NOT the 3rd) and as soon as ListView scrolls the 3rd magically turns yellow.
On my peer emulators that show this behavior, I can set a breakpoint on the fist line of onClick and I see that the BP is in fact not hit until the ListView is scrolled.
This behavior doesn't make any sense to me. I would expect the onClick behavior to be unrelated to the encapsulating View's scrolling efforts.
Any thoughts on why this might be the case and how I can rectify the situation so that onClicks always happen immediately when the button is touched?
Thanks.
Post Scriptus: ArrayAdapter and ListView code requested:
public class MonthArrayAdapter extends ArrayAdapter<Date> {
private MonthView[] _views;
private Vector<Procedure<Date>> _dateSelectionChangedListeners = new Vector<Procedure<Date>>();
public MonthArrayAdapter(Context context, int textViewResourceId, Date minSelectableDay, Date maxSelectableDay) {
super(context, textViewResourceId);
int zeroBasedMonth = minSelectableDay.getMonth();
int year = 1900 + minSelectableDay.getYear();
if(minSelectableDay.after(maxSelectableDay))
{
throw new IllegalArgumentException("Min day cannot be after max day.");
}
Date prevDay = minSelectableDay;
int numMonths = 1;
for(Date i = minSelectableDay; !sameDay(i, maxSelectableDay); i = i.addDays(1) )
{
if(i.getMonth() != prevDay.getMonth())
{
numMonths++;
}
prevDay = i;
}
_views = new MonthView[numMonths];
for(int i = 0; i<numMonths; i++)
{
Date monthDate = new Date(new GregorianCalendar(year, zeroBasedMonth, 1, 0, 0).getTimeInMillis());
Date startSunday = findStartSunday(monthDate);
this.add(monthDate);
_views[i] = new MonthView(this.getContext(), startSunday, minSelectableDay, maxSelectableDay);
zeroBasedMonth++;
if(zeroBasedMonth == 12)
{
year++;
zeroBasedMonth = 0;
}
}
for(final MonthView a : _views)
{
a.addSelectedDateChangedListener(new Procedure<MonthView>()
{
#Override
public void execute(MonthView input) {
for(final MonthView b: _views)
{
if(a != b)
{
b.clearCurrentSelection();
}
}
for(Procedure<Date> listener : _dateSelectionChangedListeners)
{
listener.execute(a.getSelectedDate());
}
}
});
}
}
void addSelectedDateChangedListener(Procedure<Date> listener)
{
_dateSelectionChangedListeners.add(listener);
}
private boolean sameDay(Date a, Date b)
{
return a.getYear() == b.getYear() && a.getMonth() == b.getMonth() &&
a.getDate() == b.getDate();
}
#Override
public View getView (int position, View convertView, ViewGroup parent)
{
return _views[position];
}
private Date findStartSunday(Date d)
{
return d.subtractDays(d.getDay());
}
public void setSelectedDate(Date date)
{
for(MonthView mv : _views)
{
mv.setSelectedDate(date);
}
}
}
and
public class DatePicker extends ActivityBase {
public static final String CHOSEN_DATE_RESULT_KEY = "resultKey";
public static final String MIN_SELECTABLE_DAY = DatePicker.class.getName() + "MIN";
public static final String MAX_SELECTABLE_DAY = DatePicker.class.getName() + "MAX";
private static final String SELECTED_DATE = UUID.randomUUID().toString();
private long _selectedDate = -1;
private MonthArrayAdapter _monthArrayAdapter;
#Override
public void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
Date now = new Date();
Bundle inputs = this.getIntent().getExtras();
long min = inputs.getLong(MIN_SELECTABLE_DAY, 0);
Date minSelectableDate;
if(min == 0)
{
minSelectableDate = new Date(now);
}
else
{
minSelectableDate = new Date(min);
}
Log.i(DatePicker.class.getName(), "min date = " + minSelectableDate.toString());
long max = inputs.getLong(MAX_SELECTABLE_DAY, 0);
Date maxSelectableDate;
if(max == 0)
{
maxSelectableDate = new Date(now.addDays(35).getTime());
}
else
{
maxSelectableDate = new Date(max);
}
setContentView(R.layout.date_picker);
Button doneButton = (Button) findViewById(R.id.DatePickerDoneButton);
if(doneButton == null)
{
Log.e(this.getClass().getName(), "Could not find doneButton from view id.");
finish();
return;
}
doneButton.setOnClickListener(new OnClickListener()
{
#Override
public void onClick(View v) {
Intent result = new Intent();
result.putExtra(CHOSEN_DATE_RESULT_KEY, _selectedDate);
setResult(RESULT_OK, result);
finish();
}
});
Button cancelButton = (Button) findViewById(R.id.DatePickerCancelButton);
if(cancelButton == null)
{
Log.e(this.getClass().getName(), "Could not find cancelButton from view id.");
finish();
return;
}
cancelButton.setOnClickListener(new OnClickListener()
{
#Override
public void onClick(View v) {
setResult(RESULT_CANCELED, null);
finish();
}
});
ListView lv = (ListView)findViewById(R.id.DatePickerMonthListView);
lv.setDividerHeight(0);
_monthArrayAdapter =
new MonthArrayAdapter(this, android.R.layout.simple_list_item_1, minSelectableDate, maxSelectableDate);
_monthArrayAdapter.addSelectedDateChangedListener(new Procedure<Date>()
{
#Override
public void execute(Date input) {
_selectedDate = input.getTime();
}
});
lv.setAdapter(_monthArrayAdapter);
}
#Override
public void onRestoreInstanceState(Bundle savedInstanceState)
{
if(savedInstanceState.containsKey(SELECTED_DATE))
{
_selectedDate = savedInstanceState.getLong(SELECTED_DATE);
_monthArrayAdapter.setSelectedDate(new Date(_selectedDate));
}
}
#Override
public void onSaveInstanceState(Bundle savedInstanceState)
{
savedInstanceState.putLong(SELECTED_DATE, _selectedDate);
}
}
Having the same problem, looking for an answer. I totally didn't believe it when I didn't get my onClick method until I scrolled my list. I'll post the answer here if I find it.
Right now, my best guess is to try different events besides click (because the scroll space is eating the complex touch events that turn into a click event):
"downView" is a static variable to track the element being clicked.
view.setOnTouchListener(new View.OnTouchListener() {
#Override
public boolean onTouch(View v, MotionEvent event) {
if (event.getAction() == MotionEvent.ACTION_DOWN) {
downView = v;
return true;
} else if (event.getAction() == MotionEvent.ACTION_UP) {
if (downView == v) {
handleClick(v);
return true;
}
downView = null;
}
return false;
}
});
The main reason is that ListView doesn't like an adapter having an array of views.
So the problem is triggered by
public View getView (int position, View convertView, ViewGroup parent)
{
return _views[position];
}
When looking at the ListView code (or rather it's parents AbsListView.obtainView method) you'll see code like
if (scrapView != null) {
...
child = mAdapter.getView(position, scrapView, this);
...
if (child != scrapView) {
mRecycler.addScrapView(scrapView);
It can happen that getView(position,...) is called with scrapView != _views[position] and hence scrapView will be recycled. On the other hand, it is quite likely that the same view is also added again to ListView, resulting in views having a weird state (see this issue)
Ultimately, this should be fixed in ListView IMO, but temporarily, I advise against using an adapter containing an array of views.
So I'll add a completely separate answer to this outside of manually composing your own click events from touch events.
I traded some emails with the Android Team (there's a few perks from being consumed by the googly) and they suggested that my attempt to implement ListAdapter by hand was inefficient and that if I don't correctly hook up the data observer methods of the adapter it can cause "funny problems with event handling."
So I did the following:
1) Replaced my implementation of ListAdapter with a subclass of BaseAdapter that overrode the necessary functions.
2) Stopped using list.invalidateViews() and started using adapter.notifyDataChanged()
and the bug seems to have gone away.
That's more work than manually composing a click event, but it's also more correct code in the long run.
Aswer is:
public View getView(int position, View convertView, ViewGroup parent) {
View v=makeMyView(position);
v.setFocusableInTouchMode(false);
return v;
}