I'm trying to achieve a visual effect, that if I could make would look awesome! The login of the app that I'm doing looks like this:
Keep in mind that the image on the background is an animation, that makes a slightly transition from that image to another.
What I want is make the title of the app "Akrasia" be transparent, but transparent meaning that you can see the image in background through the title letters, this means that in some way I must override the onDraw method of the RelativeLayout that contains this form. I tried to do that, but the only thing that I got was errors. Maybe I'm wrong trying to override the onDraw method in boths, the TextView and the RelativeLayout, maybe there's an easiest way to do it. What do you think? Or maybe is impossible to achive this effect?
UPDATE:
This is how it should look like.
Also I tried to make a custom view extending from TextView wich has a method setBackgroundView wich stores a view instance into a field. Later on the onDraw method and I managed to get the bitmap from the background image. But I don't know how draw it using canvas.
UPDATE:
I make it work! Now I only need change that blue-like background by the drawable of the background.
The view:
final public class SeeThroughTextView extends TextView
{
Bitmap mMaskBitmap;
Canvas mMaskCanvas;
Paint mPaint;
Drawable mBackground;
Bitmap mBackgroundBitmap;
Canvas mBackgroundCanvas;
boolean mSetBoundsOnSizeAvailable = false;
public SeeThroughTextView(Context context)
{
super(context);
init();
}
private void init() {
mPaint = new Paint();
mPaint.setXfermode(new PorterDuffXfermode(PorterDuff.Mode.DST_OUT));
}
public SeeThroughTextView(Context context, AttributeSet attrs) {
super(context, attrs);
init();
}
public SeeThroughTextView(Context context, AttributeSet attrs, int defStyleAttr) {
super(context, attrs, defStyleAttr);
init();
}
#Override
#Deprecated
public void setBackgroundDrawable(Drawable bg)
{
mBackground = bg;
int w = bg.getIntrinsicWidth();
int h = bg.getIntrinsicHeight();
// Drawable has no dimensions, retrieve View's dimensions
if (w == -1 || h == -1)
{
w = getWidth();
h = getHeight();
}
// Layout has not run
if (w == 0 || h == 0)
{
mSetBoundsOnSizeAvailable = true;
return;
}
mBackground.setBounds(0, 0, w, h);
invalidate();
}
#Override
public void setBackgroundColor(int color)
{
setBackgroundDrawable(new ColorDrawable(color));
}
#Override
protected void onSizeChanged(int w, int h, int oldw, int oldh)
{
super.onSizeChanged(w, h, oldw, oldh);
mBackgroundBitmap = Bitmap.createBitmap(w, h, Bitmap.Config.ARGB_8888);
mBackgroundCanvas = new Canvas(mBackgroundBitmap);
mMaskBitmap = Bitmap.createBitmap(w, h, Bitmap.Config.ARGB_8888);
mMaskCanvas = new Canvas(mMaskBitmap);
if (mSetBoundsOnSizeAvailable)
{
mBackground.setBounds(0, 0, w, h);
mSetBoundsOnSizeAvailable = false;
}
}
#Override
protected void onDraw(Canvas canvas)
{
// Draw background
mBackground.draw(mBackgroundCanvas);
// Draw mask
mMaskCanvas.drawColor(Color.BLACK, PorterDuff.Mode.CLEAR);
super.onDraw(mMaskCanvas);
mBackgroundCanvas.drawBitmap(mMaskBitmap, 0.f, 0.f, mPaint);
canvas.drawBitmap(mBackgroundBitmap, 0.f, 0.f, null);
}
}
And in my fragment I have this because the animation in the background:
vBackground.addOnLayoutChangeListener(new View.OnLayoutChangeListener() {
#Override
public void onLayoutChange(View v, int left, int top, int right, int bottom, int oldLeft, int oldTop, int oldRight, int oldBottom) {
vTitle.setBackgroundDrawable(new BitmapDrawable(vBackground.getDrawingCache()));
vTitle.invalidate();
}
});
Nailed!
The view:
final public class SeeThroughTextView extends TextView
{
Bitmap mMaskBitmap;
Canvas mMaskCanvas;
Paint mPaint;
Drawable mBackground;
Bitmap mBackgroundBitmap;
Canvas mBackgroundCanvas;
boolean mSetBoundsOnSizeAvailable = false;
public SeeThroughTextView(Context context)
{
super(context);
init();
}
private void init() {
Typeface myTypeface = Typeface.createFromAsset(getContext().getAssets(), "fonts/gillsans.ttf");
setTypeface(myTypeface);
mPaint = new Paint();
mPaint.setXfermode(new PorterDuffXfermode(PorterDuff.Mode.DST_OUT));
}
public SeeThroughTextView(Context context, AttributeSet attrs) {
super(context, attrs);
init();
}
public SeeThroughTextView(Context context, AttributeSet attrs, int defStyleAttr) {
super(context, attrs, defStyleAttr);
init();
}
#Override
#Deprecated
public void setBackgroundDrawable(Drawable bg)
{
mBackground = bg;
int w = bg.getIntrinsicWidth();
int h = bg.getIntrinsicHeight();
// Drawable has no dimensions, retrieve View's dimensions
if (w == -1 || h == -1)
{
w = getWidth();
h = getHeight();
}
// Layout has not run
if (w == 0 || h == 0)
{
mSetBoundsOnSizeAvailable = true;
return;
}
mBackground.setBounds(0, 0, w, h);
invalidate();
}
#Override
public void setBackgroundColor(int color)
{
setBackgroundDrawable(new ColorDrawable(color));
}
#Override
protected void onSizeChanged(int w, int h, int oldw, int oldh)
{
super.onSizeChanged(w, h, oldw, oldh);
mBackgroundBitmap = Bitmap.createBitmap(w, h, Bitmap.Config.ARGB_8888);
mBackgroundCanvas = new Canvas(mBackgroundBitmap);
mMaskBitmap = Bitmap.createBitmap(w, h, Bitmap.Config.ARGB_8888);
mMaskCanvas = new Canvas(mMaskBitmap);
if (mSetBoundsOnSizeAvailable)
{
mBackground.setBounds(0, 0, w, h);
mSetBoundsOnSizeAvailable = false;
}
}
#Override
protected void onDraw(Canvas canvas)
{
// Draw background
mBackground.draw(mBackgroundCanvas);
// Draw mask
mMaskCanvas.drawColor(Color.BLACK, PorterDuff.Mode.CLEAR);
super.onDraw(mMaskCanvas);
mBackgroundCanvas.drawBitmap(mMaskBitmap, 0.f, 0.f, mPaint);
canvas.drawBitmap(mBackgroundBitmap, 0.f, 0.f, null);
}
}
In my fragment:
#Override
public void onViewCreated(View view, Bundle savedInstanceState) {
super.onViewCreated(view, savedInstanceState);
vLoginBtn = (Button) view.findViewById(R.id.btn_login);
vRegistrationBtn = (Button) view.findViewById(R.id.btn_registration);
vForgotBtn = (Button) view.findViewById(R.id.btn_forgot);
vBackground = (KenBurnsView) view.findViewById(R.id.login_background);
vTitle = (SeeThroughTextView) view.findViewById(R.id.txt_view_login_title);
vBackground.setResourceUrls(
"http://www.youwall.com/papel/peaceful_place_wallpaper_4f3f3.jpg",
"http://www.fwallpaper.net/wallpapers/P/E/Peaceful-Scenary_1920x1200.jpg",
"http://p1.pichost.me/i/39/1620902.jpg"
);
vBackground.addOnLayoutChangeListener(new View.OnLayoutChangeListener() {
#Override
public void onLayoutChange(View v, int left, int top, int right, int bottom, int oldLeft, int oldTop, int oldRight, int oldBottom) {
vTitle.setBackgroundDrawable(getResources().getDrawable(R.drawable.drawable_background_login_top));
vTitle.invalidate();
vBackground.removeOnLayoutChangeListener(this);
}
});
}
The drawables are just two shapes, one with the top-left corner and top-right corner with radius 10dp and the another one with the radius in the bottoms.
The custom TextView with the top drawable shape is alligned above the RelativeLayout wich contains the EditTexts.
No much rocket science. Thanks a lot to #Klotor for suggesting the idea!
Specify a new color in your res/values/colors.xml file (create one if it doesn't exist), the file might look like:
<?xml version="1.0" encoding="utf-8"?>
<resources>
<color name="ltGray">#33999999</color>
</resources>
where the first two numbers are transparency (00 - fully transparent, FF - fully opaque).
Then simply set the text color of desired TextView to #color/ltGray in the xml of that layout, or go
tvTitle.setTextColor(getResources().getColor(R.color.ltGray))
after instatiating the TextView.
Related
I'm using a custom class (extension of ImageView) to have an XML round image view. The problem is when I call .setColorFilter() it doesn't adhere to the same circular/round bounds.
How can I make the color filter only affect the image and not the entire rectangle of the view?
Here is my custom class for reference:
public class RoundedCornerImageFilterView extends ImageFilterView {
public RoundedCornerImageFilterView(Context context) {
super(context);
}
public RoundedCornerImageFilterView(Context context, AttributeSet attrs) {
super(context, attrs);
}
public RoundedCornerImageFilterView(Context context, AttributeSet attrs, int defStyle) {
super(context, attrs, defStyle);
}
public void setImageFilter(int color) {
this.setColorFilter(color, PorterDuff.Mode.SRC_IN);
}
#Override
protected void onDraw(Canvas canvas) {
Drawable drawable = getDrawable();
if (drawable == null) {
return;
}
if (getWidth() == 0 || getHeight() == 0) {
return;
}
Bitmap b = ((BitmapDrawable) drawable).getBitmap();
Bitmap bitmap = b.copy(Bitmap.Config.ARGB_8888, true);
int w = getWidth();
int h = getHeight();
Bitmap roundedCornerBitmap = getRoundedCornerBitmap(bitmap, h, w);
canvas.drawBitmap(roundedCornerBitmap, 0, 0, null);
}
public static Bitmap getRoundedCornerBitmap(Bitmap bitmap, int height, int width) {
Bitmap sbmp;
Bitmap output = Bitmap.createBitmap(bitmap.getWidth(),
bitmap.getHeight(), Bitmap.Config.ARGB_8888);
Canvas canvas = new Canvas(output);
final int color = 0xff424242;
final Paint paint = new Paint();
final Rect rect = new Rect(0, 0,
(width), (height));
final RectF rectF = new RectF(rect);
final float roundPx = 28;
paint.setAntiAlias(true);
canvas.drawARGB(0, 0, 0, 0);
paint.setColor(color);
canvas.drawRoundRect(rectF, roundPx, roundPx, paint);
paint.setXfermode(new PorterDuffXfermode(PorterDuff.Mode.SRC_IN));
canvas.drawBitmap(bitmap, rect, rect, paint);
return output;
}
}
My xml implementation:
<MY_PATH.RoundedCornerImageFilterView
android:id="#+id/MY_IMAGE_VIEW"
android:layout_width="match_parent"
android:layout_height="150dp"
/>
Me trying to set the color filter:
MY_IMAGE_VIEW.setColorFilter(Color.parseColor(color), PorterDuff.Mode.OVERLAY)
Before the filter (looking like it's supposed to):
After setting the filter (you can see the square edges now):
Although the image (bitmap) has been given rounded corners, the canvas that it is written to has not. Since the color filter is being applied to the canvas, the tint spills out into the corners.
I suggest that you apply a rounded rectangle to a path then clip the path to the canvas. Something like this:
public class RoundedImageView extends AppCompatImageView {
private final Path mPath = new Path();
public RoundedImageView(Context context) {
super(context);
init();
}
public RoundedImageView(Context context, #Nullable AttributeSet attrs) {
super(context, attrs);
init();
}
public RoundedImageView(Context context, #Nullable AttributeSet attrs, int defStyleAttr) {
super(context, attrs, defStyleAttr);
init();
}
private void init() {
setColorFilter(Color.RED, PorterDuff.Mode.OVERLAY);
}
#Override
protected void onSizeChanged(int w, int h, int oldw, int oldh) {
super.onSizeChanged(w, h, oldw, oldh);
mPath.reset();
mPath.addRoundRect(new RectF(0, 0, w, h), 128, 128, Path.Direction.CW);
}
#Override
public void draw(Canvas canvas) {
canvas.save();
canvas.clipPath(mPath);
super.draw(canvas);
canvas.restore();
}
}
I am using ImageView here, but the concept remains the same.
If you do this type of clipping, then rounding the bitmap becomes superfluous since it will also be clipped to the path.
I want to makes one layer fade into the beneath layers (or transparency depends on how you see it) according to a gradient. A so-called transparent (alpha) gradient mask. I am looking for a solution similar to this but on android instead of ios:
I have tried this solution but as mentioned in the comments, the overlay is not making the layer beneath transparent, it only makes it fade to a specified color.
Any suggestions?
You could use Android-FadingEdgeLayout library.
Or here's a subclassed framelayout based on the said lib:
public class AlphaGradientLayout extends FrameLayout {
private static final int DEFAULT_GRADIENT_SIZE_DP = 80;
public static final int FADE_EDGE_TOP = 1;
private static final int DIRTY_FLAG_TOP = 1;
private static final int[] FADE_COLORS = new int[]{Color.TRANSPARENT, Color.BLACK};
private boolean fadeTop;
private int gradientSizeTop;
private Paint gradientPaintTop;
private Rect gradientRectTop;
private int gradientDirtyFlags;
public AlphaGradientLayout(Context context) {
super(context);
init(null, 0);
}
public AlphaGradientLayout(Context context, AttributeSet attrs) {
super(context, attrs);
init(attrs, 0);
}
public AlphaGradientLayout(Context context, AttributeSet attrs, int defStyleAttr) {
super(context, attrs, defStyleAttr);
init(attrs, 0);
}
private void init(AttributeSet attrs, int defStyleAttr) {
int defaultSize = (int) TypedValue.applyDimension(TypedValue.COMPLEX_UNIT_DIP, DEFAULT_GRADIENT_SIZE_DP,
getResources().getDisplayMetrics());
if (attrs != null) {
TypedArray arr = getContext().obtainStyledAttributes(attrs, R.styleable.AlphaGradientLayout, defStyleAttr, 0);
int flags = arr.getInt(R.styleable.FadingEdgeLayout_fel_edge, 0);
fadeTop = (flags & FADE_EDGE_TOP) == FADE_EDGE_TOP;
gradientSizeTop = arr.getDimensionPixelSize(R.styleable.FadingEdgeLayout_fel_size_top, defaultSize);
if (fadeTop && gradientSizeTop > 0) {
gradientDirtyFlags |= DIRTY_FLAG_TOP;
}
arr.recycle();
} else {
gradientSizeTop = defaultSize;
}
PorterDuffXfermode mode = new PorterDuffXfermode(PorterDuff.Mode.DST_IN);
gradientPaintTop = new Paint(Paint.ANTI_ALIAS_FLAG);
gradientPaintTop.setXfermode(mode);
gradientRectTop = new Rect();
}
#Override
public void setPadding(int left, int top, int right, int bottom) {
if (getPaddingTop() != top) {
gradientDirtyFlags |= DIRTY_FLAG_TOP;
}
super.setPadding(left, top, right, bottom);
}
#Override
protected void onSizeChanged(int w, int h, int oldw, int oldh) {
super.onSizeChanged(w, h, oldw, oldh);
if (h != oldh) {
gradientDirtyFlags |= DIRTY_FLAG_TOP;
}
}
#Override
protected void dispatchDraw(Canvas canvas) {
int newWidth = getWidth(), newHeight = getHeight();
boolean fadeAnyEdge = fadeTop;
if (getVisibility() == GONE || newWidth == 0 || newHeight == 0 || !fadeAnyEdge) {
super.dispatchDraw(canvas);
return;
}
if ((gradientDirtyFlags & DIRTY_FLAG_TOP) == DIRTY_FLAG_TOP) {
gradientDirtyFlags &= ~DIRTY_FLAG_TOP;
int actualHeight = getHeight() - getPaddingTop() - getPaddingBottom();
int size = Math.min(gradientSizeTop, actualHeight);
int l = getPaddingLeft();
int t = getPaddingTop();
int r = getWidth() - getPaddingRight();
int b = t + size;
gradientRectTop.set(l, t, r, b);
LinearGradient gradient = new LinearGradient(l, t, l, b, FADE_COLORS, null, Shader.TileMode.CLAMP);
gradientPaintTop.setShader(gradient);
}
int count = canvas.saveLayer(0.0f, 0.0f, (float) getWidth(), (float) getHeight(), null, Canvas.ALL_SAVE_FLAG);
super.dispatchDraw(canvas);
if (fadeTop && gradientSizeTop > 0) {
canvas.drawRect(gradientRectTop, gradientPaintTop);
}
canvas.restoreToCount(count);
}
}
Then move the items that you want faded inside this layout
And here's what you should get
I want to make a circular suface view (porthole effect). Surface view is inside a Frame layout. I want to make a custom view that i can add to Frame layout on top of surface view and mask whole Frame layout to produce porthole effect so that surface view will be shown as circle.
I searched and a lot for answer on Web and Stackoverflow but failed.
Then i saw this question and i tried this custom view to mask frame layout(and hence surfaceview) but i am not getting the desired result.
What i want is a custom view that can take height and width of it's parent (parent is square in shape) and make a transparent circle at it's center touching all four sides at middle of the boundaries, rest(view - circle) of the view will be of color that i can set.
public class FocusView extends View {
private Paint mTransparentPaint;
private Paint mSemiBlackPaint;
private Path mPath = new Path();
public static float radius , xCor , yCor;
public FocusView(Context context) {
super(context);
initPaints();
}
public FocusView(Context context, AttributeSet attrs) {
super(context, attrs);
initPaints();
}
public FocusView(Context context, AttributeSet attrs, int defStyleAttr) {
super(context, attrs, defStyleAttr);
initPaints();
}
private void initPaints() {
mTransparentPaint = new Paint();
mTransparentPaint.setColor(Color.GREEN);
mTransparentPaint.setStrokeWidth(10);
mSemiBlackPaint = new Paint();
mSemiBlackPaint.setColor(Color.TRANSPARENT);
mSemiBlackPaint.setStrokeWidth(10);
}
#Override
protected void onDraw(Canvas canvas) {
super.onDraw(canvas);
mPath.reset();
mPath.addCircle(xCor,yCor,radius, Path.Direction.CW);
mPath.setFillType(Path.FillType.INVERSE_EVEN_ODD);
canvas.drawCircle(xCor,yCor,radius, mTransparentPaint);
canvas.drawPath(mPath, mSemiBlackPaint);
canvas.clipPath(mPath);
canvas.drawColor(Color.parseColor("#FFFFFF")); //A6000000
}
}
Please if somebody can help me. Thanks in advance.
This is an example of a view that paints the whole view pink and cuts a centered, circular hole making the parent visible:
public class FocusView extends View {
private Paint mCutPaint = new Paint(Paint.ANTI_ALIAS_FLAG);
private Bitmap mBitmap;
private Canvas mInternalCanvas;
public FocusView(Context context) {
super(context);
init();
}
public FocusView(Context context, AttributeSet attrs) {
super(context, attrs);
init();
}
public FocusView(Context context, AttributeSet attrs, int defStyle) {
super(context, attrs, defStyle);
init();
}
private void init() {
mCutPaint.setXfermode(new PorterDuffXfermode(PorterDuff.Mode.CLEAR));
}
#Override
protected void onSizeChanged(int w, int h, int oldw, int oldh) {
super.onSizeChanged(w, h, oldw, oldh);
if (mInternalCanvas != null) {
mInternalCanvas.setBitmap(null);
mInternalCanvas = null;
}
if (mBitmap != null) {
mBitmap.recycle();
mBitmap = null;
}
mBitmap = Bitmap.createBitmap(w, h, Bitmap.Config.ARGB_8888);
mInternalCanvas = new Canvas(mBitmap);
}
#Override
protected void onDraw(Canvas canvas) {
if (mInternalCanvas == null || mBitmap == null) {
return;
}
final int width = getWidth();
final int height = getHeight();
// make the radius as large as possible within the view bounds
final int radius = Math.min(width, height) / 2;
mInternalCanvas.drawColor(0xFFFF00FF);
mInternalCanvas.drawCircle(width / 2, height / 2, radius, mCutPaint);
canvas.drawBitmap(mBitmap, 0, 0, null);
}
}
The reason for drawing to an internal Bitmap first is that if you apply PorterDuff.Mode.CLEAR to the original Canvas it will cut away everything that's been previously drawn to the canvas, including the parent view.
There may be better solutions out there, but this one is simple enough to understand.
I have little problem with viewPager. I have ViewPager with 4 frgaments which displayed RoundedImageView. The RoundedImageView has a rounded corners. When I was swiped from right to left then a corners is not rounded. That look like this:
I set white rounded backgorund for viewpager. When I not displayed a RoundedImageView(is hide) then all is ok and I have always rounded background in view pager.
I tried set clipChildren and I failed. I don't have idea to resolve my problem.
[EDIT:]
I have another problem with RoundedClipingLayout: W/OpenGLRenderer﹕ Bitmap too large to be uploaded into a texture (1726620832x0, max=4096x4096)
I use this solution. If somebody want to check or use:
public class RoundClippingLinearLayout extends LinearLayout {
private RectF rect;
private int mCornerRadius = 10;
public RoundClippingLinearLayout(Context context, AttributeSet attrs) {
super(context, attrs);
onInit();
}
public RoundClippingLinearLayout(Context context) {
super(context);
onInit();
}
protected void onInit() {
mCornerRadius = getResources().getDimensionPixelOffset(R.dimen.radius);
setWillNotDraw(false);
setLayerType(View.LAYER_TYPE_SOFTWARE, null);
}
#Override
protected void onSizeChanged(int w, int h, int oldw, int oldh) {
super.onSizeChanged(w, h, oldw, oldh);
if (w != oldw && h != oldh) {
rect = new RectF(0, 0, w, h);
}
}
#Override
protected void dispatchDraw(Canvas canvas) {
canvas.setDrawFilter(new PaintFlagsDrawFilter(1, Paint.ANTI_ALIAS_FLAG));
Path clipPath = new Path();
clipPath.addRoundRect(rect, mCornerRadius, mCornerRadius, Path.Direction.CW);
canvas.clipPath(clipPath);
super.dispatchDraw(canvas);
}
}
I am trying to do something very simple (see above). I want all of the pixels of a canvas to be a solid color, except for the the pixels that fill a centered circle. I have read hundreds stack overflow post on this subject and have tried hundreds of things including setting the PorterDuff.Mode. Here is my current onDraw() of MyView extends View:
protected void onDraw(Canvas canvas) {
int w = canvas.getWidth();
int h = canvas.getHeight();
Paint framePaint = new Paint();
framePaint.setStyle(Paint.Style.FILL);
framePaint.setColor(Color.BLUE);
canvas.drawRect(0, 0, w, h, framePaint);
Paint transparentPaint = new Paint();
transparentPaint.setColor(Color.TRANSPARENT);
transparentPaint.setAntiAlias(true);
canvas.drawCircle(w / 2, h / 2, (w + h) / 4, transparentPaint);
}
Am I misunderstanding something, why cant I paint over an existing pixel with transparent paint. When I do this the pixel stays the same. When I use PorterDuff, the pixel turns black. Please help.
Try this:
public class TransparentCircle extends View {
Bitmap bm;
Canvas cv;
Paint eraser;
public TransparentCircle(Context context) {
super(context);
Init();
}
public TransparentCircle(Context context, AttributeSet attrs) {
super(context, attrs);
Init();
}
public TransparentCircle(Context context, AttributeSet attrs,
int defStyleAttr) {
super(context, attrs, defStyleAttr);
Init();
}
private void Init(){
eraser = new Paint();
eraser.setXfermode(new PorterDuffXfermode(PorterDuff.Mode.CLEAR));
eraser.setAntiAlias(true);
}
#Override
protected void onSizeChanged(int w, int h, int oldw, int oldh) {
if (w != oldw || h != oldh) {
bm = Bitmap.createBitmap(w, h, Bitmap.Config.ARGB_8888);
cv = new Canvas(bm);
}
super.onSizeChanged(w, h, oldw, oldh);
}
#Override
protected void onDraw(Canvas canvas) {
int w = getWidth();
int h = getHeight();
int radius = w > h ? h / 2 : w / 2;
bm.eraseColor(Color.TRANSPARENT);
cv.drawColor(Color.BLUE);
cv.drawCircle(w / 2, h / 2, radius, eraser);
canvas.drawBitmap(bm, 0, 0, null);
super.onDraw(canvas);
}
}