5

This is a difficult situation to describe, so please bear with me. I've been asked to make a 'coach marks' screen a little bit like this:

enter image description here

However, in my case I need to 'pull' some of the underlying images to the front and display them in a different color to make them stand out. I cannot determine what the 'frame' of these images will be before run time. My approach is to add a black, semi-opaque view (I call it a 'curtain') covering the entire screen, and then add the images that I need as subviews of the curtain with a different color. EDIT This part is easy and I am not asking for help with this (which is why the suggestion that this is a duplicate of the suggested question is incorrect).

I don't believe that I can use bringToFront() or setTranslationZ() because these methods will not bring the ImageView all the way to the front of my curtain. So, what I am trying to to do is get the exact 'frame' of the image view (its x, y, width, and height) relative to the overall screen and then try to add the new image view using the same drawable with the same frame.

It's also important to know that the underlying 'view' is actually made up of multiple views (including a sliding drawer), which is why I need to get the coordinates relative to the screen. I hope this makes sense.

I am able to draw the new ImageView as a subview of the curtain with the correct color, but the position and size of the image is way off. How can I draw this new ImageView exactly on top of the previous one?

I'm also open to other strategies (since this one seems to not be working). Thank you for any help.

@Override
protected void onCreate(Bundle savedInstanceState) {
    super.onCreate(savedInstanceState);
    setContentView(R.layout.activity_main);

    ...

    ViewTreeObserver vto = mRootview.getViewTreeObserver();
    vto.addOnGlobalLayoutListener(new ViewTreeObserver.OnGlobalLayoutListener() {
        @Override
        public void onGlobalLayout() {

            mRootview.getViewTreeObserver().removeOnGlobalLayoutListener(this);

            mDrawerLayout.openDrawer(Gravity.LEFT);

            RelativeLayout curtain = addOpaqueCurtainToRootView();
            int frame[] = getFrameOfExistingImageView();
            addNewImageViewOnTopOfEverything(curtain, frame);
        }
    });
}

private RelativeLayout addOpaqueCurtainToRootView() {
    RelativeLayout curtain = new RelativeLayout(context);
    RelativeLayout.LayoutParams curtainParams = new RelativeLayout.LayoutParams(RelativeLayout.LayoutParams.MATCH_PARENT, RelativeLayout.LayoutParams.MATCH_PARENT);
    curtain.setLayoutParams(curtainParams);
    curtain.setBackgroundColor(Color.argb(179, 0, 0, 0)); //70% opaque
    mRootview.addView(curtain);
    return curtain;
}

private int[] getFrameOfExistingImageView() {
    // proving that I have a reference to the original image
    mCurrentImage.setColorFilter(Color.rgb(255, 0, 0));

    int array[] = new int[2];
    mCurrentImage.getLocationOnScreen(array);
    Log.d(TAG, "width: " + Integer.toString(array[0]));
    Log.d(TAG, "height: " + Integer.toString(array[1]));
    Log.d(TAG, "x: " + Float.toString(mCurrentImage.getX()));
    Log.d(TAG, "y: " + Float.toString(mCurrentImage.getY()));

    int frame[] = new int[4]; // x,y,width,height
    frame[0] = (int)mCurrentImage.getX();
    frame[1] = (int)mCurrentImage.getY();
    frame[2] = array[0];
    frame[3] = array[1];
    return frame;
}

private void addNewImageViewOnTopOfEverything(RelativeLayout curtain, int[] frame) {
    ImageView iv = new ImageView(context);
    iv.setImageResource(R.drawable.imgView);
    iv.setColorFilter(Color.rgb(255, 255, 255));
    iv.setX(frame[0]);
    iv.setY(frame[1]);
    iv.setLayoutParams(new RelativeLayout.LayoutParams(frame[2], frame[3]));

    curtain.addView(iv);
}
AndroidDev
  • 20,466
  • 42
  • 148
  • 239
  • Possible duplicate of [How to create transparent activity in android?](http://stackoverflow.com/questions/16332064/how-to-create-transparent-activity-in-android) – Siarhei Jul 16 '16 at 07:33
  • 1
    try [this](http://pastebin.com/dg4CXx0f) – pskink Jul 16 '16 at 07:47
  • @user5599807 - not a duplicate. That post just asks a very simple question, which is how to draw a view with a semi-transparent background. I'm already doing that (easily). My question is about how to place child views on that new view in a precise position and with a precise size. – AndroidDev Jul 16 '16 at 15:42
  • @pskink - thanks for this. When I read this code, it just seems to suggest that the solution is to a) get a reference to the image view, b) remove it from its initial parent view, and then c) add it to the new translucent view. But that doesn't solve the question of how to position and size that image view in the new parent, right? I'm probably not understanding how to implement this suggestion correctly. Is there more than what I am seeing? Thanks very much for anything further you can suggest. I appreciate it! – AndroidDev Jul 16 '16 at 15:46
  • no `ImageView` needed: draw anything you want in `dispatchDraw` method, you can call for example `canvas.drawBitmap`, `MyDecorView` takes all the screen including action bar so you dont need to position it in any way, just call `addMeToTheViewTree` after calling `setContentView` – pskink Jul 16 '16 at 15:57
  • Ok. I guess I have some reading to do on the Canvas class. Thanks. – AndroidDev Jul 16 '16 at 17:12

1 Answers1

0

I figured this out. I had two basic problems.

First, I was getting the frame of the ImageView all wrong. I needed to get the left and top coordinates and also the height and width of the image after it is drawn. This is how I am doing that (I'm actually converting to left, top, right, bottom, but the rest is easy):

private int[] getFrameOfImageView(ImageView imageView) {

    int array[] = new int[2];
    imageView.getLocationOnScreen(array);

    int frame[] = new int[4];

    frame[0] = array[0]; // left
    frame[1] = array[1]; // top
    frame[2] = array[0] + imageView.getWidth(); // right
    frame[3] = array[1] + imageView.getHeight(); // bottom

    return frame;
}

Then, I created a new class to do the drawing. This is the biggest difference from my OP. Originally, I was thinking that I could just add this as a new ImageView and set its LayoutParams to place it where I wanted. Instead, I wound up using Paint and Canvas to do the drawing, sizing, and placement:

public class CanvasPainter extends View {

    Context mContext;
    Drawable mDrawable;

    int mFrame[];
    int left;
    int top;
    int right;
    int bottom;
    int width;
    int height;

    private final int LEFT = 0;
    private final int TOP = 1;
    private final int RIGHT = 2;
    private final int BOTTOM = 3;

    public CanvasPainter(Context context, int frame[], Drawable drawable) {
        super(context);

        mContext = context;
        mFrame = frame;
        mDrawable = drawable;

        left = frame[LEFT];
        top = frame[TOP];
        right = frame[RIGHT];
        bottom = frame[BOTTOM];
        width = right - left;
        height = bottom - top;

    }

    public void onDraw(Canvas canvas) {
        Paint paint = new Paint();
        paint.setStyle(Paint.Style.FILL);


        Bitmap b = ((BitmapDrawable) mDrawable).getBitmap();
        Bitmap bitmapResized = Bitmap.createScaledBitmap(b, width, height, false);

        canvas.drawBitmap(bitmapResized, left, top, paint);
    }
}

So, put this together, it all gets organized from onCreate() like this;

@Override 
protected void onCreate(Bundle savedInstanceState) {
    super.onCreate(savedInstanceState);
    setContentView(R.layout.activity_main);

    ... 

    mDrawerLayout.openDrawer(Gravity.LEFT);

    ViewTreeObserver vto = mRootview.getViewTreeObserver();
    vto.addOnGlobalLayoutListener(new ViewTreeObserver.OnGlobalLayoutListener() {
        @Override 
        public void onGlobalLayout() { 

            mRootview.getViewTreeObserver().removeOnGlobalLayoutListener(this);

            RelativeLayout curtain = addOpaqueCurtainToRootView();
            int frame[] = getFrameOfExistingImageView();
            Drawable d = ContextCompat.getDrawable(context, R.drawable.imgView);
            CanvasPainter canvasPainter = new CanvasPainter(context, mCurrentImg, frame, d);
            curtain.addView(canvasPainter);
        } 
    }); 
} 

Might be a little bit of an obscure problem, but I hope this helps someone.

AndroidDev
  • 20,466
  • 42
  • 148
  • 239