I use a ViewGroup as a container to place multiple instances of a smaller-sized icon image at arbitrary locations. When the icons are placed near the bottom or right edge of the container, they are automatically scaled to fit within the bounds of the ViewGroup. Why does this happen, and how do I prevent it? I set the clipChildren attribute on the ViewGroup to FALSE to see if that worked, but it doesn't.
The ViewGroup is added dynamically. m_iconContainer is the ViewGroup which parents the icons.
m_outerContainer = (ViewGroup) v.findViewById( R.id.outer );
m_iconContainer = new ScalingFrameLayout( getActivity() );
m_outerContainer.addView( m_iconContainer );
Icons are added in a straightforward manner:
PinImage p = new PinImage( getActivity() );
FrameLayout.LayoutParams params = (FrameLayout.LayoutParams) p.getLayoutParams();
params.leftMargin = pt.x - PIN_IMAGE_XOFFSET;
params.topMargin = pt.y - PIN_IMAGE_YOFFSET;
m_iconContainer.addView( p );
public class ScalingFrameLayout extends FrameLayout {
private float scale = 1;
public ScalingFrameLayout(Context context) {
super(context);
setWillNotDraw(false);
setClipChildren(false);
}
public void setScale(float factor) {
scale = factor;
invalidate();
}
public float getScale() {
return scale;
}
#Override
public void onDraw(Canvas canvas) {
canvas.scale(scale, scale);
super.onDraw(canvas);
}
}
protected class PinImage extends ImageView {
public PinImage( Context c ) {
super( c );
setLayoutParams( new FrameLayout.LayoutParams( LayoutParams.WRAP_CONTENT, LayoutParams.WRAP_CONTENT ));
setImageResource( R.drawable.droppin );
this.setOnClickListener( new OnClickListener() {
public void onClick( View v ) {
onCommentPinTouched( v );
}
});
}
}
Related
I am writing an app that will draw a bubble on Canvas.
I have MainActivity with its layout as a simple LinearLayout which I use as holder for fragment.
My fragment has no xml layout as I am drawing on Canvas, so I set its layout programmatically like this:
public class BubbleFragment extends Fragment {
Bubble bubble;
#Override
public void onCreate(Bundle savedInstanceState){
super.onCreate(savedInstanceState);
//retain fragment
setRetainInstance(true);
//bubble = new Bubble(getActivity()); //THIS WILL CRASH APP, MOVE TO onCreateView instetad
}
#Override
public View onCreateView(LayoutInflater inflater, ViewGroup container, Bundle savedInstanceState)
{
LinearLayout.LayoutParams lp = new LinearLayout.LayoutParams(
ViewGroup.LayoutParams.MATCH_PARENT,
ViewGroup.LayoutParams.MATCH_PARENT,
100);
LinearLayout ll = new LinearLayout(getActivity());
ll.setOrientation(LinearLayout.VERTICAL);
// instantiate my class that does drawing on Canvas
bubble = new Bubble(getActivity());
bubble.setLayoutParams(lp);
bubble.setBackgroundColor(Color.BLUE);
ll.addView(bubble); //if you create bubble in onCreate() this will crash. Create bubble in onCreateView
return ll;
}
}
So, when I start my app, I am expecting bubble to show in the middle of the screen and move slowly towards bottom. Since I use setRetainInstance(true) above, I am expecting that when I rotate my screen, the bubble will continue where it left off before rotation. However, it is redraws at its initial location (middle of the screen).
I would like to to continue drawing itself from the position where it was before screen orientation changed, not from the beginning.
Here is my bubble code:
public class Bubble extends View {
private static final boolean BUBBLING = true; //thread is running to draw
private Paint paint;
private ShapeDrawable bubble;
// coordiantes, radius etc
private int x;
private int y;
private int dx;
private int dy;
private int r;
private int w = 400;
private int h = 400;
//handler to invalidate (force redraw on main GUI thread from this thread)
private Handler handler = new Handler();
public Bubble(Context context) {
super(context);
WindowManager wm = (WindowManager) context.getSystemService(Context.WINDOW_SERVICE);
Display display = wm.getDefaultDisplay();
Point size = new Point();
display.getSize(size);
w = size.x;
h = size.y;
x = w/2;
y = h/2;
r = 60;
dx = 1;
dy = 1;
bubble = new ShapeDrawable(new OvalShape());
bubble.getPaint().setColor(Color.RED);
bubble.setBounds(0, 0, r, r);
paint = new Paint();
paint.setAntiAlias(true);
paint.setStrokeWidth(10);
}
#Override
protected void onSizeChanged (int w, int h, int oldw, int oldh){
//set bubble parameters (center, size, etc)
startAnimation();
}
public void startAnimation(){
new Thread(new Runnable() {
public void run() {
while (BUBBLING) {
moveBubble();
try {
Thread.sleep(200);
} catch (InterruptedException e) {
}
//update by invalidating on main UI thread
handler.post(new Runnable() {
public void run() {
invalidate();
}
});
}
}
}).start();
}
#Override
public void onDraw(Canvas canvas) {
super.onDraw(canvas);
canvas.save();
// draws bubble on canvas
canvas.translate(dx, dy);
bubble.draw(canvas);
canvas.restore();
}
private void moveBubble(){
dx += 1;
dy += 1;
x = x + dx;
y = y + dy;
if (bubble.getPaint().getColor() == Color.YELLOW){
bubble.getPaint().setColor(Color.RED);
} else {
bubble.getPaint().setColor(Color.YELLOW);
}
}
}
Much appreciated,
If you really want to retain an entire view, you could do something like a lazy load:
public class BubbleFragment extends Fragment {
Bubble bubble;
LinearLayout parent;
#Override
public void onCreate(Bundle savedInstanceState){
super.onCreate(savedInstanceState);
setRetainInstance(true);
}
#Override
public void onAttach(Activity activity) {
super.onAttach(activity);
parent = new LinearLayout(activity);
if(bubble == null) {
LinearLayout.LayoutParams lp = new LinearLayout.LayoutParams(
ViewGroup.LayoutParams.MATCH_PARENT,
ViewGroup.LayoutParams.MATCH_PARENT,
100);
bubble = new Bubble(getActivity());
bubble.setLayoutParams(lp);
bubble.setBackgroundColor(Color.BLUE);
}
parent.setOrientation(LinearLayout.VERTICAL);
parent.addView(bubble);
}
#Override
public void onDetach() {
super.onDetach();
parent.removeView(bubble);
}
#Override
public View onCreateView(LayoutInflater inflater, ViewGroup container, Bundle savedInstanceState)
{
return parent;
}
}
In your case you don't need setRetainInstance(true), you just need to save instance variables in onSaveInstanceState() and load them in onCreate(). For your example, something like:
public void onCreate(Bundle b) {
super.onCreate(b);
if(b != null) {
xPos = b.getInt("x");
yPos = b.getInt("y");
}
}
public void onSaveInstanceState(Bundle b) {
super.onSaveInstanceState(b);
b.putInt("x",xPos);
b.putInt("y",yPos);
}
By the way, the reason you can't create Bubble in onCreate() is because the Fragment is not fully associated with its Activity until onActivityCreated() is called, which comes after onCreate() is called.
I have the following custom relative layout:
public class CircleView extends RelativeLayout implements OnDragListener {
private DropCallback onDrop = null;
private ImageButton imageButton = null;
private int radius = -1;
private double step = -1;
private double angle = -1;
private static final int CENTER_ID = 111;
public CircleView(Context context, DropCallback onDrop, int radius, List<View> views) {
super(context);
this.onDrop = onDrop;
this.radius = radius;
this.step = (2 * Math.PI) / views.size();
this.initView(context, views);
}
private void initView(Context context, List<View> views) {
RelativeLayout.LayoutParams layoutParamsView = new RelativeLayout.LayoutParams(RelativeLayout.LayoutParams.MATCH_PARENT, RelativeLayout.LayoutParams.MATCH_PARENT);
this.setLayoutParams(layoutParamsView);
RelativeLayout.LayoutParams layoutParams = new RelativeLayout.LayoutParams(RelativeLayout.LayoutParams.WRAP_CONTENT, RelativeLayout.LayoutParams.WRAP_CONTENT);
layoutParams.addRule(RelativeLayout.CENTER_HORIZONTAL);
layoutParams.addRule(RelativeLayout.CENTER_VERTICAL);
this.imageButton = new ImageButton(context);
this.imageButton.setId(CENTER_ID);
this.imageButton.setLayoutParams(layoutParams);
this.imageButton.setImageResource(R.drawable.ic_power_on);
this.imageButton.getBackground().setAlpha(0);
this.imageButton.setOnDragListener(this);
this.addView(this.imageButton);
for(View view : views) {
this.addView(this.placeView(view));
}
}
private View placeView(View view) {
view.measure(0, 0);
this.imageButton.measure(0, 0);
int x = (int)((view.getMeasuredWidth() / 2) + this.radius * Math.cos(this.angle));
int y = (int)((view.getMeasuredHeight() / 2) + this.radius * Math.sin(this.angle));
this.angle += this.step;
int deltaX = view.getMeasuredWidth();
int deltaY = view.getMeasuredHeight();
int deltaImageX = this.imageButton.getMeasuredWidth() / 2;
int deltaImageY = this.imageButton.getMeasuredHeight() / 2;
int xToDraw = ((x - deltaX) - deltaImageX);
int yToDraw = ((y - deltaY) - deltaImageY);
RelativeLayout.LayoutParams layoutParams = new RelativeLayout.LayoutParams(RelativeLayout.LayoutParams.WRAP_CONTENT, RelativeLayout.LayoutParams.WRAP_CONTENT);
layoutParams.addRule(RelativeLayout.ABOVE, CENTER_ID);
layoutParams.addRule(RelativeLayout.RIGHT_OF, CENTER_ID);
layoutParams.setMargins(xToDraw, 0, 0, yToDraw);
view.setLayoutParams(layoutParams);
return view;
}
#Override
public boolean onDrag(View view, DragEvent event) {
return this.onDrop.onDrop(view, event);
}
}
I have no xml layout file for this and it worked until now.
Today i changed the activity using this custom layout to be a Fragment and i now need to set the layout. Unfortunately something like the following isnt possible:
#Override
public View onCreateView(LayoutInflater inflater, ViewGroup container, Bundle savedInstanceState) {
...
return inflater.inflate(new CircleView(this, this, 240, this.views), container, false);
}
So how can i inflate my custom relative layout?
Just create an instance of Circle view and add it to container :
#Override
public View onCreateView(LayoutInflater inflater, ViewGroup container, Bundle savedInstanceState) {
...
CircleView circleView = new CircleView(this, this, 240, this.views);
...
return circleView;
}
You should create you xml layout, for example circle_view.xml:
<your_package.CircleView android:id="#+id/gcircle_view"
android:layout_width="fill_parent"
android:layout_height="fill_parent"/>
and then:
#Override
public View onCreateView(LayoutInflater inflater, ViewGroup container, Bundle savedInstanceState) {
...
return inflater.inflate(R.layout.circle_view, container, false);
}
btw, you should implement constructors:
public CircleView(Context context){
super(context);
}
public CircleView(Context context, AttributeSet attrs)
{
super(context, attrs);
}
public CircleView(Context context, AttributeSet attrs, int defStyle) {
super(context, attrs, defStyle);
}
You can create the inflator like this:
LayoutInflater inflater = LayoutInflater.from(context);
Then use the inflator to inflate the needed.
When I'm trying to scale my canvas to a draw SCALED view, my view is actually scaled, but view is getting clipped. (probably because of its layout parameters?)
public void onDraw(Canvas canvas) {
canvas.scale(2f, 2f);
view.draw(canvas);
}
simple image:
image after new onDraw called, for example when I click this button:
The button should be full sized when canvas is scaled. Do you have any ideas how to solve it?
p.s. call of
view.invalidate();
view.requestLayout();
doesn't help.
I'm using MyDragShadowBuilder because I want my view to be double sized when I drag the view.
private final class MyDragShadowBuilder extends DragShadowBuilder {
public MyDragShadowBuilder(View view) {
super(view);
}
#Override
public void onDrawShadow(Canvas canvas) {
final View view = getView();
if (view != null) {
canvas.scale(2f, 2f);
view.draw(canvas);
} else {
Log.e("DragShadowBuilder", "Asked to draw drag shadow but no view");
}
}
I add my view into my Absolute Layout implementation with WRAP_CONTENT layout properties
I ran into the same trouble. After some time i found a way to make it work :)
This scales the original view by a factor of 4.
private static class MyDragShadowBuilder extends View.DragShadowBuilder {
private static final int SCALING_FACTOR = 4;
public MyDragShadowBuilder(View view) {
super(view);
}
#Override
public void onProvideShadowMetrics(Point shadowSize, Point shadowTouchPoint) {
View v = getView();
final int width = v.getWidth() * SCALING_FACTOR;
final int height = v.getHeight() * SCALING_FACTOR;
shadowSize.set(width, height);
shadowTouchPoint.set(width / 2, height / 2);
}
#Override
public void onDrawShadow(Canvas canvas) {
canvas.scale(SCALING_FACTOR, SCALING_FACTOR);
getView().draw(canvas);
}
}
I have a custom view and want to add one more custom view on that custom view. This is my custom view class:
public class CustomCircle extends View{
float radius;
Paint paint = new Paint();
String message = "";
public CustomCircle(Context context, AttributeSet attrs) {
super(context, attrs);
}
#Override
protected void onDraw(Canvas canvas) {
super.onDraw(canvas);
canvas.drawCircle(getWidth()/2, getHeight()/2,radius, paint);
}
#Override
public boolean onTouchEvent(MotionEvent event) {
boolean isClickInCircle = false;
float x = event.getX();
float y = event.getY();
double check = Math.sqrt((x-getWidth()/2)*(x-getWidth()/2) + (y-getHeight()/2)*(y-getHeight()/2));
if (check<=radius) {
isClickInCircle= true;
}
if (isClickInCircle) {
switch (event.getAction()) {
case MotionEvent.ACTION_DOWN:
Toast.makeText(getContext(),message, Toast.LENGTH_LONG).show();
return true;
case MotionEvent.ACTION_UP:
Toast.makeText(getContext(), message, Toast.LENGTH_LONG).show();
return true;
default:
break;
}
}
return false;
}
and am using another class that extends LinearLayout:
public class B extends LinearLayout {
private Paint paint;
public B(Context context) {
super(context);
paint = new Paint();
paint.setColor(Color.WHITE);
paint.setStyle(Style.FILL);
}
public void addCircle() {
CustomCircle circleBlue = new CustomCircle(getContext(), null);
circleBlue.paint.setColor(Color.WHITE);
circleBlue.paint.setAntiAlias(true);
circleBlue.radius = 160;
circleBlue.message = "Clicked";
addView(circleBlue);
CustomCircle circleRed = new CustomCircle(getContext(), null);
circleRed.paint.setColor(Color.RED);
circleRed.paint.setAntiAlias(true);
circleRed.radius = 80;
circleRed.message = "Clicked";
addView(circleRed);
}
and I'm calling the B class from the main activity class using :
#Override
public void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
B root = new B(this);
root.addCircle();
setContentView(root);
}
The output is showing me only one circle instead of a circle inside another circle. What's wrong with my code?
And the Output is showing me only One circle instead of circle inside
circle.
You picked the wrong layout if you want to overlap children, a RelativeLayout or FrameLayout is the way to go. Also, regarding your code:
public class B extends RelativeLayout {
//...
public void addCircle() {
// the constructor that uses the AttributeSet should be added if you use the
// custom component in the xml layout
CustomCircle circleBlue = new CustomCircle(getContext());
// ...
// add it with LayoutParams
RelativeLayout.LayoutParams rlp = new RelativeLayout.LayoutParams(
RelativeLayout.LayoutParams.WRAP_CONTENT,
RelativeLayout.LayoutParams.WRAP_CONTENT);
rlp.addRule(RelativeLayout.CENTER_IN_PARENT);
addView(circleBlue, rlp);
// the same for the other view
}
Also your two circle will have the same dimensions so they will overlap perfectly(and you'll not be able to see them), you would need to give them different dimensions, through the LayoutParams, for example:
RelativeLayout.LayoutParams rlp = new RelativeLayout.LayoutParams(200, 200);
for the first one and:
RelativeLayout.LayoutParams rlp = new RelativeLayout.LayoutParams(100, 100);
for the second one.
Is it possible that there are 2 circles in the layout but you can see only one
because it has the same color as the first circle and they are laying on top of each other?
change circleRed.paint.setColor(Color.WHITE);
to circleRed.paint.setColor(Color.BLACK);
and see what it gives
I have listview similar to this:
Add code, it isn't much, but maybe helpful:
CustomAdapter:
public class MyAdapter extends BaseAdapter{
private LayoutInflater mInflater;
private Animation fadeIn;
private Animation fadeOut;
public MyAdapter(Activity c) {
mInflater = c.getLayoutInflater();
fadeIn = AnimationUtils.loadAnimation(c.getApplicationContext(), R.anim.alpha_show);
fadeIn.setFillAfter(true);
fadeOut = AnimationUtils.loadAnimation(c.getApplicationContext(), R.anim.alpha_dissappear);
fadeOut.setFillAfter(true);
}
public int getCount() {
return MainButtonsList.getList().getSize() +
GlobalPrefs.getEmptyRowsAtEnd() +
GlobalPrefs.getEmptyRowsAtStart();
}
public View getView(int position, View v, ViewGroup parent) {
View convertView = v;
if (convertView == null) {
convertView = mInflater.inflate(R.layout.main_list_item, null);
}
ImageView iv = (ImageView) convertView.findViewById(R.id.image);
if ((position > GlobalPrefs.getEmptyRowsAtStart() - 1) && (position < getCount() - GlobalPrefs.getEmptyRowsAtEnd())) {
iv.setImageResource(MainButtonsList.getList().getListImageResource(position - GlobalPrefs.getEmptyRowsAtStart()));
iv.setAlpha(255);
iv.setTag(MainButtonsList.UNPRESSED_BUTTON_TAG);
} else {
iv.setTag(MainButtonsList.UNUSED_BUTTON_TAG);
iv.setAlpha(0);
iv.setVisibility(0);
iv.setClickable(false);
iv.setImageResource(R.drawable.logo_list_null);
}
iv.setMaxHeight(GlobalPrefs.getRowHeight());
iv.setMaxWidth(GlobalPrefs.getRowWidth());
iv.setBackgroundResource(0);
return convertView;
}
public Object getItem(int position) {
return MainButtonsList.getList().getObject(position);
}
public long getItemId(int position) {
return position;
}
}
Custom ListView:
public class CopyOfListView3d extends ListView{
private final Camera mCamera = new Camera();
private final Matrix mMatrix = new Matrix();
private Context context;
private Paint mPaint;
public CopyOfListView3d(Context context, AttributeSet attrs) {
super(context, attrs);
this.context = context;
this.setChildrenDrawingOrderEnabled(true);
}
#Override
protected int getChildDrawingOrder (int childCount, int i) {
//sets order number to each child, so makes overlap and center is always on top
}
#Override
protected boolean drawChild(Canvas canvas, View child, long drawingTime) {
// get top left coordinates
boolean isCenter = false;
final int top = child.getTop();
final int bottom = child.getBottom();
Bitmap bitmap = child.getDrawingCache();
if (bitmap == null) {
child.setDrawingCacheEnabled(true);
child.buildDrawingCache();
bitmap = child.getDrawingCache();
}
final int centerY = child.getHeight() / 2;
final int centerX = child.getWidth() / 2;
final int radius = getHeight() / 2;
final int absParentCenterY = getTop() + getHeight() / 2;
final int absChildCenterY = child.getTop() + centerY;
final int distanceY = (absParentCenterY - absChildCenterY) / 2;
final int absDistance = Math.min(radius, Math.abs(distanceY));
final float translateZ = (float) Math.sqrt((radius * radius) - (absDistance * absDistance));
mCamera.save();
float myTranslateX = (float) (translateZ * (1.5f));
int density = GlobalPrefs.getDensity();
if (density < DisplayMetrics.DENSITY_LOW) {
myTranslateX = (float) myTranslateX - 80;
} else if (density == DisplayMetrics.DENSITY_LOW) {
myTranslateX = (float) myTranslateX - GlobalPrefs.getScreenWidth() + density + 40;
} else if (density <= DisplayMetrics.DENSITY_MEDIUM) {
myTranslateX = (float) myTranslateX - ((float)(GlobalPrefs.getScreenWidth()*0.75)) + density/2;
} else
if (density <= DisplayMetrics.DENSITY_HIGH) {
myTranslateX = (float) myTranslateX - 320;
} else
if (density > DisplayMetrics.DENSITY_HIGH) {
//Log.i("density", "this is more than high");
myTranslateX = (float) myTranslateX;
}
if ((top < absParentCenterY) && (bottom > absParentCenterY)) {
//make center row bigger
isCenter = true;
mCamera.translate((float) myTranslateX, 0, (float) -160);//130
child.setPressed(true);
child.setTag(MainButtonsList.PRESSED_BUTTON_TAG);
}
else {
//top
child.setTag(MainButtonsList.UNPRESSED_BUTTON_TAG);
child.setPressed(false);
mCamera.translate((float) myTranslateX, 0, -150);//120;
};
mCamera.getMatrix(mMatrix);
mCamera.restore();
// create and initialize the paint object
if (mPaint == null) {
mPaint = new Paint();
mPaint.setAntiAlias(true);
mPaint.setFilterBitmap(true);
}
//calculates alpha for each row, so far from center, rows are barely visible
mPaint.setAlpha(calculateAlpha(absChildCenterY));
mMatrix.postTranslate((float) (-centerX * 1.5f), top);
canvas.drawBitmap(bitmap, mMatrix, mPaint);
return false;
}
}
They are made of ImageViews where white color is selected state and blue color is unselected state and I change selected/unselected by screen coordinates, so center always is selected.
I have custom 3dListView which extends ListView and made custom adapter, where i set imageviews to rows. Code is too much to show, but there are pretty simple
One of these could be helpfull:
1) How can I make fade in (maybe also fade out) for center row (I can find which row is center when giving parent or childCount)? (Better)
2) It also could be helpfull if there is any OnStateChange listener, which listens to view's presses. Then I could fade in anytime when view becomes pressed and fadeout, when looses press.
I found an answer myself. Added this code piece to my custom ListView in drawChild method end:
final ImageView iv = (ImageView) child.findViewById(R.id.image);
child.setTag(MainButtonsList.PRESSED_BUTTON_TAG);
Animation anim = fadeOut();
anim.setAnimationListener(new Animation.AnimationListener() {
public void onAnimationStart(Animation animation) {
// TODO Auto-generated method stub
}
public void onAnimationRepeat(Animation animation) {
// TODO Auto-generated method stub
}
public void onAnimationEnd(Animation animation) {
child.setPressed(true);
iv.startAnimation(fadeIn());
}
});
iv.clearAnimation();
iv.startAnimation(anim);
In list item layout, added another image, which put under item and now it looks prety nice.
You'll want to do the fading in the adapter for the listview.
In your adapter
private List<YourListObject> deleteItems;
// Constructor to init list and other vars
#Override
public View getView(int position, View convertView, ViewGroup parent) {
// Draw stuff
// including your object
for(YourListObject obj : deleteItems){
if(currentObj.equals(obj)){
Animation fadeOutAnimation = new AlphaAnimation(1.0f, 0.0f);
fadeOutAnimation.setDuration(300);
DeleteAnimationListener listener = new DeleteAnimationListener(currentObj);
fadeOutAnimation.setAnimationListener(listener);
someView.startAnimation(fadeOutAnimation);
}
}
return someView;
}
public void remove(YourListObject obj){
deleteItems.add(obj);
notifyDataSetChanged();
}
protected class DeleteAnimationListener extends EndAnimationListener {
private final YourListObject obj;
public DeleteAnimationListener(YourListObject obj) {
this.obj = obj;
}
#Override
public void onAnimationEnd(Animation animation) {
yourObjects.remove(obj);
deleteItems.remove(obj);
notifyDataSetChanged();
}
};
currentObj is the view in the list your currently manipulating
yourObjects is the list you are storing the data in.
deleteItems is a new list.
You call .remove(obj) on your adapter to remove one.
The getView will then remove it from the list (with a fade out animation) once this animation is finished the AnimationListener will remove the actual object from your dataset.