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.
Related
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.
finishing up an android app, I have four methods each pertaining to their respective buttons. each is a color. if one is pressed, and its the correct color being told to be pressed it gets a +point, else it gets a -point.
these are the 4 methods im trying to combine into one, though I am having trouble figuring out that if i did, then I wouldnt have a way to to assign negative points. I was thinking that if i did like if blue, add points, else if teal add points etc... but this approach takes away from the fact that if they were told to press blue and pressed teal then it wouldnt register to add a -point.
here is the code:
public void blue_pressed(View view) {
correct = (TextView) findViewById(R.id.right);
incorrect = (TextView) findViewById(R.id.wrong);
if (count != 0 && !(count >NUMBER_ROUNDS) && color == blue) {
cor++;
correct.setText(getString(R.string.num_cor, cor));
} else {
inc++;
incorrect.setText(getString(R.string.num_inc, inc));
}
start_pressed(view);
}
public void teal_pressed(View view) {
correct = (TextView) findViewById(R.id.right);
incorrect = (TextView) findViewById(R.id.wrong);
if (count != 0 && !(count > NUMBER_ROUNDS) && color == teal) {
cor++;
correct.setText(getString(R.string.num_cor, cor));
} else {
inc++;
incorrect.setText(getString(R.string.num_inc, inc));
}
start_pressed(view);
}
public void purp_pressed(View view) {
correct = (TextView) findViewById(R.id.right);
incorrect = (TextView) findViewById(R.id.wrong);
if (count != 0 && !(count > NUMBER_ROUNDS) && color == purp) {
cor++;
correct.setText(getString(R.string.num_cor, cor));
} else {
inc++;
incorrect.setText(getString(R.string.num_inc, inc));
}
start_pressed(view);
}
public void pink_pressed(View view) {
correct = (TextView) findViewById(R.id.right);
incorrect = (TextView) findViewById(R.id.wrong);
if (count != 0 && !(count > NUMBER_ROUNDS) && color == pink) {
cor++;
correct.setText(getString(R.string.num_cor, cor));
} else {
inc++;
incorrect.setText(getString(R.string.num_inc, inc));
}
start_pressed(view);
}
thanks!
This question is really more about refactoring than Android.
You have 4 methods that differ only regarding the colour in the if statement. This means if you pass in the colour as a parameter, you would have only one method instead, and maintain only that. But you cannot change the signature of the "onClick" methods, so what to do? You create an additional private helper method that you call in all your four "onClick" methods.
In Eclipse you can extract easily the body of one of the "onClick" methods and refactor it into your helper method. Enter the shortcut: alt+command+M. Then change the signature of the extracted method manually to include an int parameter to compare color to. Finally you get...
private void buttonPressed(View view, int col){
correct = (TextView) findViewById(R.id.right);
incorrect = (TextView) findViewById(R.id.wrong);
if (count != 0 && !(count >NUMBER_ROUNDS) && color == col) {
cor++;
correct.setText(getString(R.string.num_cor, cor));
} else {
inc++;
incorrect.setText(getString(R.string.num_inc, inc));
}
start_pressed(view);
}
public void blue_pressed(View view) {
buttonPressed(view, blue);
}
public void teal_pressed(View view) {
buttonPressed(view, teal);
}
public void purp_pressed(View view) {
buttonPressed(view, purp);
}
public void pink_pressed(View view) {
buttonPressed(view, pink);
}
I got a problem with a quite large GridView.(about 70 children) The GridView works fine if I start it on onCreate or after resumeing after pressing the home button and then return. But when I resume after coming back from sleep mode, my BaseAdapter starts again and ruin the changes I have done to it during runtime. This also make getChildAt() give a NullPointerException if I am calling it just after restart.
How can I make it just do what regular onPause(home button) does to the GridView, and avoid that the GridView is wiped out everytime I am resumeing from sleep mode?
Edit:
I have tried setting a wakelock for my Activity class that calls the BaseAdpter with no luck
2.Edit: Since I posted this question I have played around with trying to restore the GridView using this code in onPause:
SparseArray<Parcelable> array = new SparseArray<Parcelable>();
gridView.saveHierarchyState(array);
bundle = new Bundle();
bundle.putSparseParcelableArray("state", array);
And this in onResume:
try{
gridView.restoreHierarchyState(bundle.getSparseParcelableArray("state"));
}
catch(Exception e){
//Most likely first start
Log.i("SomeTag", "No GridView state found");
}
}
The strange thing is everything I seems to have jumped from one place to another on the screen and it is still crashing when I try to getChildAt(). It is also failing to get it after sleep mode.
Edit Here is the code from BaseAdapter getView(Note! some of this code is irrelevant)
public View getView (int position, View convertView, ViewGroup parent) {
mParentView = parent;
DisplayMetrics metrics = mContext.getResources().getDisplayMetrics();
int width = metrics.widthPixels;
int height = metrics.heightPixels;
//sets the height for every individual box
int box = width/7*6/10;
ImageCell v = null;
if (convertView == null) {
// If it's not recycled, create a new ImageCell.
v = new ImageCell (mContext);
v.setLayoutParams(new GridView.LayoutParams(box, box));
v.setScaleType(ImageView.ScaleType.CENTER_CROP);
v.setPadding(0, 0, 0, 0);
} else {
v = (ImageCell) convertView;
}
v.mCellNumber = position;
v.mGrid = (GridView) mParentView;
v.mEmpty = true;
int id = 200;
v.setId(++id);
String map = str[position];
int pos = position;
int up = pos-10;
int down = pos+10;
int left = pos-1;
int right = pos+1;
if(up>=0){
above = str[up];
}
else{
//Do nothing
}
if(down<70){
under = str[down];
}
else{
//Do nothing
}
if(left<=-1){
//Do nothing
}
else{
lefte=str[left];
}
if(right>=70){
//Do nothing
}
else{
righte=str[right];
}
//if(left>-1|left!=9|left!=19|left!=29|left!=39|left!=49|left!=59){
// lefte = str[left];
// }
// else{
// Log.i("ImageCellAdapter", "Left is trying to break walls "+left);
//Do nothing
// }
if (map.equals("mountain")) {
//Checks surroundings to find out witch drawable to set
v.setBackgroundResource(R.color.cell_empty);
v.mEmpty = false;
//All
if(above=="mountain"&&under=="mountain"&&lefte=="mountain"&&righte=="mountain"){
v.setImageResource(R.drawable.mountain_full);
}
//Single
else if(above=="mountain"&&under!="mountain"&&lefte!="mountain"&&righte!="mountain"){
v.setImageResource(R.drawable.mountain_down);
}
else if(above!="mountain"&&under=="mountain"&&lefte!="mountain"&&righte!="mountain"){
v.setImageResource(R.drawable.mountain_up);
}
else if(above!="mountain"&&under!="mountain"&&lefte!="mountain"&&righte=="mountain"){
v.setImageResource(R.drawable.mountain_left);
}
else if(above!="mountain"&&under!="mountain"&&lefte=="mountain"&&righte!="mountain"){
v.setImageResource(R.drawable.mountain_right);
}
//Double
else if(above=="mountain"&&under!="mountain"&&lefte!="mountain"&&righte=="mountain"){
v.setImageResource(R.drawable.mountain_left_down);
}
else if(above!="mountain"&&under=="mountain"&&lefte!="mountain"&&righte=="mountain"){
v.setImageResource(R.drawable.mountain_left_up);
}
else if(above=="mountain"&&under!="mountain"&&lefte=="mountain"&&righte!="mountain"){
v.setImageResource(R.drawable.mountain_right_down);
}
else if(above!="mountain"&&under=="mountain"&&lefte=="mountain"&&righte!="mountain"){
v.setImageResource(R.drawable.mountain_up_right);
}
else if(above!="mountain"&&under!="mountain"&&lefte=="mountain"&&righte=="mountain"){
v.setImageResource(R.drawable.mountain_up_down);
}
else if(above=="mountain"&&under=="mountain"&&lefte!="mountain"&&righte!="mountain"){
v.setImageResource(R.drawable.mountain_up_down);
}
//Triple
else if(above!="mountain"&&under=="mountain"&&lefte=="mountain"&&righte=="mountain"){
v.setImageResource(R.drawable.mountain_left_right_down);
}
else if(above=="mountain"&&under=="mountain"&&lefte=="mountain"&&righte!="mountain"){
v.setImageResource(R.drawable.mountain_left_up_down);
}
else if(above=="mountain"&&under!="mountain"&&lefte=="mountain"&&righte=="mountain"){
v.setImageResource(R.drawable.mountain_left_up_right);
}
else if(above=="mountain"&&under=="mountain"&&lefte!="mountain"&&righte=="mountain"){
v.setImageResource(R.drawable.mountain_up_right_down);
}
//None
else{
v.setImageResource(R.drawable.mountain);
}
}
else if(map=="start"){
List<String> posOf = Arrays.asList(str);
startPos=posOf.indexOf("start");
v.mEmpty=false;
v.setBackgroundResource(R.color.cell_empty);
getDur();
BitmapDrawable first = (BitmapDrawable)mContext.getResources().getDrawable(R.drawable.gress);
BitmapDrawable second =(BitmapDrawable)mContext.getResources().getDrawable(R.drawable.ic_launcher);
BitmapDrawable third = (BitmapDrawable)mContext.getResources().getDrawable(R.drawable.gress);
BitmapDrawable fourth = (BitmapDrawable)mContext.getResources().getDrawable(R.drawable.ic_launcher);
final AnimationDrawable ani = new AnimationDrawable();
ani.addFrame(first, duration);
ani.addFrame(second, duration);
ani.addFrame(third, duration);
ani.addFrame(fourth, duration);
ani.setOneShot(true);
v.setImageDrawable(ani);
checkIfAnimationDone(ani);
v.post(new Runnable() {
public void run() {
ani.start();
}
});
}
else if(map=="stop"){
v.mEmpty=false;
v.setBackgroundResource(R.color.cell_empty);
v.setImageResource(R.drawable.ic_launcher);
v.setTag(1);
}
else if(map=="grass"){
v.mEmpty=false;
v.setBackgroundResource(R.drawable.gress);
}
else{
// v.setBackgroundResource (R.color.drop_target_enabled);
v.setBackgroundResource (R.color.cell_empty);
}
//v.mGrid.requestDisallowInterceptTouchEvent (true);
//v.setImageResource (R.drawable.hello);
// Set up to relay events to the activity.
// The activity decides which events trigger drag operations.
// Activities like the Android Launcher require a long click to get a drag operation started.
return v;
}
And defining the GridView in onCreate:
gridView= new BoxView(this);
if(Build.VERSION.SDK_INT >= Build.VERSION_CODES.ICE_CREAM_SANDWICH){
w.getDefaultDisplay().getSize(size);
Measuredwidth = size.x;
Measuredheight = size.y;
}else{
Display d = w.getDefaultDisplay();
Measuredwidth = d.getWidth();
Measuredheight = d.getHeight();
}
int width = Measuredwidth/7*6;
gridView.setLayoutParams(new GridView.LayoutParams(width,LayoutParams.FILL_PARENT, Gravity.CENTER_HORIZONTAL));
gridView.setNumColumns(columns);
gridView.setVerticalSpacing(0);
gridView.setHorizontalSpacing(0);
gridView.setPadding(0, 0, 0, 0);
gridView.setId(101);
gridView.setSelector(android.R.color.transparent);
gridView.setAdapter (new ImageCellAdapter(this, MAP));
I have noticed one thing in you code:
gridView.setLayoutParams(new GridView.LayoutParams(width,LayoutParams.FILL_PARENT, Gravity.CENTER_HORIZONTAL));
Even if GridView is a ViewGroup, you can't access it's LayoutParams. Just think it trough, if this would be possible that means you could put a GridView inside another GridView.
Fix this before going further because is messing with you.
If you want your GridView to be inside a LinearLayout, for example, try this:
gridView.setLayoutParams(new LinearLayout.LayoutParams(width,LayoutParams.FILL_PARENT, Gravity.CENTER_HORIZONTAL));
Here is the correct implementation of this answer:
public class MainActivity extends Activity {
#Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
/*You have to implement what things from the gridView sould be "saved"*/
GridView gridView = new GridView(this) {
#Override
public Parcelable onSaveInstanceState() {
// Create the Parceable object with the things you want to save
Parceable stateOfGrid = ....etc
return stateOfGrid;
}
#Override
public void onRestoreInstanceState(Parcelable state) {
// Restore your grid's parameters that you previously implemented in onSaveInstanceState
super.onRestoreInstanceState(state);
...
}
};
}
#Override
protected void onRestoreInstanceState(Bundle savedInstanceState) {
// TODO Auto-generated method stub
super.onRestoreInstanceState(savedInstanceState);
if (savedInstanceState != null) {
Parcelable state = savedInstanceState.getParcelable("state");
if (state != null) {
gridView.onRestoreInstanceState(state);
Log.d(this.getClass().getName(), "state restored!");
}
}
#Override
protected void onSaveInstanceState(Bundle outState) {
// Put your grid's Parceable into the bundle
super.onSaveInstanceState(outState);
Parcelable state = gridView.onSaveInstanceState();
outState.putParcelable("state", state);
}
}
Try using this tag:
android:configChanges="keyboard|keyboardHidden|orientation|screenSize"
You can use onSaveInstanceState (Bundle outState)
to save state of your gridview
#Override
protected void onSaveInstanceState(Bundle outState) {
super.onSaveInstanceState(outState);
Parcelable state = gridView.onSaveInstanceState();
outState.putParcelable("state", state);
}
then in onCreate after you seted adapter to grdiview add this code
if (savedInstanceState != null) {
Parcelable state = savedInstanceState.getParcelable("state");
if (state != null) {
gridView.onRestoreInstanceState(state);
Log.d(this.getClass().getName(), "state restored!");
}
}
Is your application running in Landscape Mode?
if yes, then you should consider adding the tag
"android:configChanges="keyboard|keyboardHidden|orientation" for your activity in Manifest.xml, which will prevent android system from killing your activity and restarting it when you unlock the screen.
I'm doing a rather round about way of making a solitaire app. What's happening right now is that I'm trying to click on a button that is supposed to deal cards from the deck to the state that you can play cards from. However, the listener is never triggering.
I put a system.out.println statement in to test to see if it ever enters the code block with the listener, and it does not.
public class SolitaireGame extends Activity {
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.game);
buttonSound = MediaPlayer.create(SolitaireGame.this, R.raw.button_click);
//instance variables
theDeck = new Deck();
botCardStacks = new ArrayList<BotCardStack>(7);
aceStacks = new ArrayList<AceCardStack>(4);
playableCards = new CardStack(52);
deckButton = (Button) this.findViewById(R.id.deckButton);
deckButton.setOnClickListener(new View.OnClickListener() {
#Override
public void onClick(View v) {
// TODO Auto-generated method stub
buttonSound.start();
dealCardsToPlayableStack();
}
});
As requested, here is my add cards to playable stack method
public void dealCardsToPlayableStack() {
Stack<Card> tempStack = new Stack<Card>();
int i = 0;
Card temp, temp1, temp2, temp3;
if(theDeck.getValueOfNext() == 0) {
while(!playableCards.isEmpty()) {
temp = playableCards.popCard();
temp.setVisible(false);
tempStack.push(temp);
}
while(!tempStack.isEmpty()) {
theDeck.addCard(tempStack.pop());
}
}
else if(theDeck.getValueOfNext() >= 3) {
temp1 = theDeck.popCard();
temp1.setVisible(true);
temp2 = theDeck.popCard();
temp2.setVisible(true);
temp3 = theDeck.popCard();
temp3.setVisible(true);
playableCards.addCard(temp3);
playableCards.addCard(temp2);
playableCards.addCard(temp1);
}
else if(theDeck.getValueOfNext() == 2) {
temp1 = theDeck.popCard();
temp1.setVisible(true);
temp2 = theDeck.popCard();
temp2.setVisible(true);
playableCards.addCard(temp2);
playableCards.addCard(temp1);
}
else if(theDeck.getValueOfNext() == 1) {
temp1 = theDeck.popCard();
temp1.setVisible(true);
playableCards.addCard(temp1);
}
}
First you should use buttonSound.prepare() before buttonSound.start() (I prefer using SoudPool Class instead of Mediaplayer for sound effects as it is less costly in memory). Secondly, can you share the dealCardsToPlayableStack() method. You can put it outside the onClick() method (in onCreate()) and if you have the same behaviour, then the problem is in this method, not the OnClick() method.
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;
}