package org.commcare.views;
import android.content.Context;
import android.content.res.TypedArray;
import android.graphics.Canvas;
import android.graphics.Rect;
import android.util.AttributeSet;
import android.widget.FrameLayout;
import org.commcare.dalvik.R;
/**
* A clipping frame is a simple frame layout that allows the specification (by attribute or
* android property) of an area which should be clipped. This allows the view to only display a
* portion of its content at a time, which can be used to animate "reveal" effects.
*
* Created by ctsims on 3/14/2016.
*/
public class ClippingFrame extends FrameLayout {
final private Rect mClipBounds= new Rect(0,0,1,1);
private float startX;
private float startY;
private float clipWidth;
private float clipHeight;
public ClippingFrame(Context context, AttributeSet attrs) {
super(context, attrs);
setParams(context, attrs);
}
public ClippingFrame(Context context, AttributeSet attrs, int defStyleAttr) {
super(context, attrs, defStyleAttr);
setParams(context, attrs);
}
private void setParams(Context context, AttributeSet attrs) {
if (attrs != null) {
TypedArray typedArray = context.getTheme().obtainStyledAttributes(attrs, R.styleable.ClippingView, 0, 0);
startX = typedArray.getFloat(R.styleable.ClippingView_clip_start_x, 0.5f);
startY = typedArray.getFloat(R.styleable.ClippingView_clip_start_y, 0.5f);
clipWidth = typedArray.getFloat(R.styleable.ClippingView_clip_width, 1);
clipHeight = typedArray.getFloat(R.styleable.ClippingView_clip_height, 1);
}
}
/**
* A property setter to be used by animations. Note that the name is sensitive and should
* not be changed.
*/
public void setClipWidth(float clipWidth) {
this.clipWidth = clipWidth;
recalculateClippingBounds();
this.invalidate();
}
/**
* A property setter to be used by animations. Note that the name is sensitive and should
* not be changed.
*/
public void setClipHeight(float clipHeight) {
this.clipHeight = clipHeight;
recalculateClippingBounds();
this.invalidate();
}
/**
* Recalculates the clipping rectangle based on changes to the measured size or the requested
* clipping frame. Does not invalidate or request layout for the view, and assumes that
* the view has already been measured
*/
private void recalculateClippingBounds() {
int width = this.getMeasuredWidth();
int height = this.getMeasuredHeight();
int centerX = (int)(startX * width);
int leftCoverage = (int)Math.ceil(clipWidth * centerX);
int rightCoverage = (int)Math.ceil(clipWidth * (width - centerX));
int leftStart = Math.max(0, centerX - leftCoverage);
int rightEnd = Math.min(width, centerX + rightCoverage);
int centerY = (int)(startY * height);
int topCoverage = (int)Math.ceil(clipHeight * centerY);
int bottomCoverage = (int)Math.ceil(clipHeight * (height - centerY));
int topStart = Math.max(0, centerY - topCoverage);
int bottomEnd = Math.min(height, centerY + bottomCoverage);
mClipBounds.set(leftStart, topStart, rightEnd, bottomEnd);
}
@Override
protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
super.onMeasure(widthMeasureSpec, heightMeasureSpec);
recalculateClippingBounds();
}
@Override
public void draw(Canvas canvas) {
//Clip boundary implementaiton, since we're targeting pre-jellybean.
if (mClipBounds != null) {
canvas.clipRect(mClipBounds);
}
super.draw(canvas);
}
}