so I made an app that draws bitmap into canvas as a template, and I want the user to be able add images from gallery then scale, position and rotate. currently stuck in positioning the imageView properly, when I drag and drop in different position the imageView gets little off the position I placed in.
here is the xml of my project to demonstorate the layout structure:
<?xml version="1.0" encoding="utf-8"?>
<androidx.constraintlayout.widget.ConstraintLayout
android:layout_width="match_parent"
android:layout_height="match_parent"
tools:context=".search.CreateActivity">
<HorizontalScrollView
android:layout_width="match_parent"
android:layout_height="0dp"
tools:layout_editor_absoluteX="283dp">
<!-- reault_layout contains layout and imageView:
layout for canvas the contains bitmap
layout for the image added by the user -->
<androidx.constraintlayout.widget.ConstraintLayout
android:id="#+id/result_layout"
android:layout_width="512dp"
android:layout_height="512dp"
android:orientation="vertical">
<androidx.constraintlayout.widget.ConstraintLayout
android:id="#+id/canvas_layout"
android:layout_width="512dp"
android:layout_height="512dp">
</androidx.constraintlayout.widget.ConstraintLayout>
<ImageView
android:id="#+id/iv_promo"
android:layout_width="100dp"
android:layout_height="100dp"
app:srcCompat="..."/>
</androidx.constraintlayout.widget.ConstraintLayout>
</HorizontalScrollView>
<!-- this button to add the image -->
<Button
android:id="#+id/add_img"
android:layout_width="wrap_content"
android:layout_height="wrap_content"/>
<!-- this button saves whatever in the result layout as image in internal storage-->
<Button
android:id="#+id/save"
android:layout_width="wrap_content"
android:layout_height="wrap_content"/>
</androidx.constraintlayout.widget.ConstraintLayout>
another issue is, the imageView can not be placed at the far right when I scroll, it's only placed within the visible area of the screen before scrolling.
I set the canvas layout width and height to 512dp because its the actual drawable is 512dpx512dp and if I set it to match_parent it might get smaller and harder to place small images in certain areas inside of the template.
For the template to be drawn I placed these two lines inside onCreate();
create_canvas = new CreateCanvas(this);
nCanvas_layout.addView(create_canvas);
I did not use setContentView(); to not draw the template on the entire layout, so this will draw inside the canvas_layout
here is the CreateCanvas class that draws the template:
public class CreateCanvas extends View {
Drawable temp_drawable
Bitmap temp_bitmap
public CreateCanvas(Context context) {
super(context);
this.context = context;
// DRAWABLE OF THE TEMPLATE
temp_drawable = ContextCompat.getDrawable(context, R.drawable.ic_template_master);
}
#Override
protected void onDraw(Canvas canvas) {
super.onDraw(canvas);
//drawableToBitmap() IS A CUSTOM METHOD TO CONVERT THE DRAWABLE TEMPLATE TO BITMAP
// RETURNS BITMAP
temp_bitmap = drawableToBitmap(temp_drawable);
// DRAWS THE TEMPLATE INTO CANVAS
canvas.drawBitmap(temp_bitmap, left, top, null);
}
// THE CUSTOM METHOD TO CONVERT DRAWABLE TO BITMAP
public Bitmap drawableToBitmap(Drawable drawable) {
if (drawable instanceof BitmapDrawable) {
return ((BitmapDrawable)drawable).getBitmap();
}
Bitmap bitmap = Bitmap.createBitmap(drawable.getIntrinsicWidth(), drawable.getIntrinsicHeight(), Bitmap.Config.ARGB_8888);
bitmap.setHeight(getWidth());
bitmap.setWidth(getWidth());
Canvas canvas = new Canvas(bitmap);
drawable.setBounds(0, 0, canvas.getWidth(), canvas.getHeight());
drawable.draw(canvas);
return bitmap;
}
}
the above class helps in drawing the template into canvas_layout, below canvas_layout there's animageView where the user can upload image from internal storage.
I have added these implements to get user touch events
implements View.OnTouchListener, View.OnDragListener, GestureDetector.OnGestureListener
then got these #overrides methods:
#Override
public boolean onTouch(View view, MotionEvent motionEvent) {
// the imageView that host the image added by user: iv_promo
if (view.getId() == R.id.iv_promo)
{
nGestureDetector.onTouchEvent(motionEvent);
return true;
}
return true;
}
#Override
public boolean onDown(MotionEvent motionEvent) {return false;}
#Override
public void onShowPress(MotionEvent motionEvent) {}
#Override
public boolean onSingleTapUp(MotionEvent motionEvent) {return false;}
#Override
public boolean onScroll(MotionEvent motionEvent, MotionEvent motionEvent1, float v, float v1) {return false;}
#Override
public void onLongPress(MotionEvent motionEvent) {
View.DragShadowBuilder builder = new View.DragShadowBuilder(nIv_promo);
nIv_promo.startDragAndDrop(null, builder, null, 0);
builder.getView().setOnDragListener(this);
}
#Override
public boolean onFling(MotionEvent motionEvent, MotionEvent motionEvent1, float v, float v1) {return false;}
#Override
public boolean onDrag(View view, DragEvent dragEvent) {
// I was trying thses outside if-statment to check if its working, but did not work
//nIv_promo.setX(dragEvent.getX()-(nIv_promo.getWidth()/2f));
//nIv_promo.setY(dragEvent.getY()-(nIv_promo.getHeight()/2f));
if (dragEvent.getAction() == DragEvent.ACTION_DRAG_STARTED)
{
// . . .
}
if (dragEvent.getAction() == DragEvent.ACTION_DRAG_ENTERED)
{
// . . .
}
if (dragEvent.getAction() == DragEvent.ACTION_DRAG_LOCATION)
{
//nIv_promo.setX(dragEvent.getX());
//nIv_promo.setY(dragEvent.getY());
}
if (dragEvent.getAction() == DragEvent.ACTION_DRAG_EXITED)
{
// . . .
}
if (dragEvent.getAction() == DragEvent.ACTION_DROP)
{
// . . .
}
if (dragEvent.getAction() == DragEvent.ACTION_DRAG_ENDED)
{
nIv_promo.setX(dragEvent.getX()-(nIv_promo.getWidth()/2f));
nIv_promo.setY(dragEvent.getY()-(nIv_promo.getHeight()/2f));
}
return true;
}
I did not like the DragShadowBuilder(), I wanted to drag the the actual imageView
and drop on different position on the screen. but Im too consumed by the placing the imageView accurately. if possible please help me with that too
Related
In my app, I have a button to show a drop-down menu, inside of that menu we have some options, one of this is "flip A coin",
the purpose of this option is to flip a coin easy animation, that animation appears inside a textView, and show a head-side or a tail-side of a coin instead of the text in the textView.
I have two problems:
The animation doesn't work how I want, it should appear in 1 second a coin instead of the text inside the textView, it stays there and after 2 seconds he disappears, but after the disappearance, the coin image come back inside the textView, it shouldn't reappear.
This is not a real question for a problem but more an optional question like "you know how to do that?". I 'don't know how to create a flip animation with a coin multiple rotations.
XML drop down menu:
<item
android:id="#+id/flipacoin"
android:title="#string/flipACoin" />
<item
android:id="#+id/rolladice"
android:title="#string/rollADice" />
<item
android:id="#+id/imagebackground"
android:title="#string/changeImage" />
JAVA code that calls the animation function:
#Override
public boolean onMenuItemClick(MenuItem item) {
switch (item.getItemId()){
case R.id.flipacoin:
flipACoin();
return true;
case R.id.rolladice:
Toast.makeText(this,"TODO roll a dice",Toast.LENGTH_SHORT).show();
return true;
case R.id.imagebackground:
Toast.makeText(this,"TODO image background",Toast.LENGTH_SHORT).show();
return true;
default:
return false;
}
}
JAVA animation function:
public void flipACoin(){
coin.setText(null); //this is for remove the text inside the textView
coin.setBackground(RANDOM.nextFloat() > 0.5f ? getResources().getDrawable(R.drawable.tails) : getResources().getDrawable(R.drawable.heads));
Animation fadeIn = new AlphaAnimation(0, 1);
fadeIn.setInterpolator(new DecelerateInterpolator());
fadeIn.setDuration(1000);
Animation fadeOut = new AlphaAnimation(1, 0);
fadeOut.setInterpolator(new AccelerateInterpolator());
fadeOut.setStartOffset(2000);
fadeOut.setDuration(1000);
AnimationSet animation = new AnimationSet(false);
animation.addAnimation(fadeIn);
animation.addAnimation(fadeOut);
coin.setAnimation(animation);
}
When You set background at the beginning it just stays after animation. To set back text and background to null You can add Animation listener. Below is sample application which does it:
public class MainActivity extends AppCompatActivity
{
TextView coin;
Random RANDOM;
#Override
protected void onCreate(Bundle savedInstanceState)
{
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
RANDOM = new Random();
coin = findViewById(R.id.coin);
(findViewById(R.id.click)).setOnClickListener(new View.OnClickListener()
{
#Override
public void onClick(View view)
{
flipACoin();
}
});
}
public void flipACoin()
{
coin.setText(null);
coin.setBackground(ResourcesCompat.getDrawable(getResources(),
RANDOM.nextFloat() > 0.5f ? R.drawable.ic_launcher_background : R.drawable.ic_launcher_foreground,
null
));
Animation fadeIn = new AlphaAnimation(0, 1);
fadeIn.setInterpolator(new DecelerateInterpolator());
fadeIn.setDuration(1000);
Animation fadeOut = new AlphaAnimation(1, 0);
fadeOut.setInterpolator(new AccelerateInterpolator());
fadeOut.setStartOffset(2000);
fadeOut.setDuration(1000);
AnimationSet animation = new AnimationSet(false);
animation.addAnimation(fadeIn);
animation.addAnimation(fadeOut);
// listener, it will execute function when animation starts/ends/repeats
animation.setAnimationListener(new Animation.AnimationListener()
{
#Override
public void onAnimationStart(Animation animation)
{
Log.d("MyTag", "onAnimationStart:");
}
#Override
public void onAnimationEnd(Animation animation) // when animation ends, set text and background to null
{
Log.d("MyTag", "onAnimationEnd:");
coin.setBackground(null);
coin.setText("Default");
}
#Override
public void onAnimationRepeat(Animation animation)
{
Log.d("MyTag", "onAnimationRepeat:");
}
});
coin.setAnimation(animation);
}
}
activity_main.xml:
<?xml version="1.0" encoding="utf-8"?>
<androidx.appcompat.widget.LinearLayoutCompat xmlns:android="http://schemas.android.com/apk/res/android"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:orientation="vertical"
>
<Button
android:id="#+id/click"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="click"
/>
<TextView
android:id="#+id/coin"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="Default"
/>
</androidx.appcompat.widget.LinearLayoutCompat>
Is there any way to place the image on the last line of the TextView
I use setCompoundDrawablesWithIntrinsicBounds. Some ideas about it will be good.
Also i need to handle clicks on my image only. This behavior is represented by the following code :
textView.setCompoundDrawablesWithIntrinsicBounds(0, 0, R.drawable.info, 0);
textView.setOnTouchListener(new View.OnTouchListener() {
#Override
public boolean onTouch(View v, MotionEvent event) {
if(event.getAction() == MotionEvent.ACTION_UP) {
if(event.getRawX() >= textView.getRight() - textView.getTotalPaddingRight()){
listener.onAddressClicked(livingAreas);
return true;
}
}
return true;
}
});
With Text view you can set a drawable on a determinated position.
You can use this xml example:
<TextView
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:drawableBottom="#drawable/image"
android:text="blablabla"/>
Programmatically:
public void setCompoundDrawables(Drawable left, Drawable top, Drawable right, Drawable bottom)
text_view.setCompoundDrawables(null, null, null, getDrawable(drawable_image_id));
Use SpannableString so that you can include image in your text. For code sample, check this question and answer
I have a TextView that contains a DrawableRight, what I want to do is detecting when the user presses that icon in drawableRight, is that possible ? and if it is how can I do it ?
PS: I am working inside a fragment
TextView XML
<TextView
android:id="#+id/mTitle"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:textIsSelectable="false"
android:textSize="22dp"
android:drawableRight="#mipmap/icn" //this is the drawable
/>
mTitle.setOnTouchListener(new View.OnTouchListener() {
#Override
public boolean onTouch(View v, MotionEvent event) {
final int DRAWABLE_RIGHT = 2;
if (event.getAction() == MotionEvent.ACTION_UP) {
if (event.getRawX() >= (mTitle.getRight() - mTitle.getCompoundDrawables()[DRAWABLE_RIGHT].getBounds().width())) {
//drawable pressed
return true;
}
}
return false;
}
});
I am new to android development as well as the Java programming language and would definitely appreciate some help with this issue.
Currently I have some code which creates a canvas and draws a bitmap picture onto it, this appears to work. I also have another bit of code which detects where the users finger is on the devices screen and this also works. when I try to combine the two the bitmap will display but as soon as I click the screen the app crashes.
I have a feeling that this issue is with the canvas/bitmap side of this as i have also noticed that any widgets that have added to layout vanish when the canvas/bitmap feature is running.
This is the code for the canvas/bitmap:
public class myView extends View{
Bitmap image;
DisplayMetrics metrics;
public myView(Context context) {
super(context);
metrics = context.getResources().getDisplayMetrics();
// TODO Auto-generated constructor stub
image = BitmapFactory.decodeResource(getResources(), R.drawable.bhead);
}
#Override
protected void onDraw(Canvas canvas) {
// TODO Auto-generated method stub
super.onDraw(canvas);
int width = metrics.widthPixels;
int height = metrics.heightPixels;
Matrix matrix = new Matrix();
float angle = 90;
float imageCenterX = 50;
float imageCenterY = 50;
matrix.setRotate(angle, imageCenterX, imageCenterY);
Bitmap temp;
temp = BitmapFactory.decodeResource(getResources(), R.drawable.bhead);
image = Bitmap.createScaledBitmap(temp, 100, 100,true);
matrix.postTranslate(((width/2)-50), ((height/2)-100));
canvas.drawBitmap(image, matrix, null);
}
}
and this is the code for the finger location on the screen:
public class MainActivity extends ActionBarActivity {
myView mview;
#Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
mview = new myView(this);
setContentView(mview);
}
#Override
public boolean onTouchEvent(MotionEvent event) {
int x = (int)event.getX();
int y = (int)event.getY();
switch (event.getAction()) {
case MotionEvent.ACTION_DOWN:
case MotionEvent.ACTION_MOVE:
case MotionEvent.ACTION_UP:
}
EditText txt = (EditText)findViewById(R.id.editText1);
txt.setText("X = "+ x +" Y = " + y);
return false;
}
#Override
public boolean onCreateOptionsMenu(Menu menu) {
// Inflate the menu; this adds items to the action bar if it is present.
getMenuInflater().inflate(R.menu.main, menu);
return true;
}
#Override
public boolean onOptionsItemSelected(MenuItem item) {
// Handle action bar item clicks here. The action bar will
// automatically handle clicks on the Home/Up button, so long
// as you specify a parent activity in AndroidManifest.xml.
int id = item.getItemId();
if (id == R.id.action_settings) {
return true;
}
return super.onOptionsItemSelected(item);
}
}
Your view hierarchy does not have an EditText with the id editText1. This is because you have set the content view to be a single instance of your class MyView (class names should start with uppercase) with this code in onCreate():
mview = new myView(this);
setContentView(mview);
As a result, you get a NullPointerException on the line txt.setText("X = "+ x +" Y = " + y); because the previous line cannot find the EditText.
You need to include both views in the layout. You can use MyView in XML by writing its fully qualified name. For example:
<LinearLayout
xmlns:android="http://schemas.android.com/apk/res/android"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:orientation="vertical" >
<com.your.package.name.MyView
android:id="#+id/my_view"
android:layout_width="match_parent"
android:layout_height="0dp"
android:layout_weight="1" />
<EditText
android:id="#+id/editText1"
android:layout_width="match_parent"
android:layout_height="wrap_content"
... />
</LinearLayout>
I have this code in the ACTION_DOWN of my onTouch(View v) method as follows...
if (event.getAction() == MotionEvent.ACTION_DOWN) {
DragShadowBuilder shadowBuilder = new View.DragShadowBuilder(view);
view.startDrag(null, shadowBuilder, view, 0);
myOnClickListener.onClick(view);
return true;
}
Now my view in this case is a ChessPiece. I have chessPiece.setBackground(R.color.black) and chessPiece.setImageBitmap(pawnBitmap).
Currently when I drag the chess piece, it drags the background color with it, which I dont want. So is there anyway to remove the background of the view when it's dragging so that the DragShadow is just of the bitmap?
You have to create your own dragshadow. This example helped me when I wanted to play with the dragshadow: http://lemonycode.blogspot.be/2012/11/using-custom-dragshadowbuidler-in.html
As you can see, there are two important methods:
public void onProvideShadowMetrics(Point shadowSize, Point touchPoint)
and:
public void onDrawShadow(Canvas canvas)
The first method is used to size the dragshadow and the second to actually draw your shadow. In your case, your chess piece. You could use
Bitmap bitmap = ((BitmapDrawable)image.getDrawable()).getBitmap();
to get the bitmap and alter it before drawing it on the canvas.
On OnDragListener()
private final class MyDragListener implements OnDragListener {
#Override
public boolean onDrag(View v, DragEvent event) {
switch (event.getAction()) {
case DragEvent.ACTION_DRAG_STARTED:
getResources().getDrawable(android.graphics.Color.TRANSPARENT);
break;
....
}
return true;
}
}
This should work. Good luck!