I want to have an imageView that is fully visible at the top, but further down it fades out, resulting in being fully transparent in the bottom. How do I achieve this?
I have a solution for this, but it is not perfect
class LinearGradientView : View {
var bitmap: Bitmap
private val paint = Paint()
constructor(context: Context?) : super(context)
constructor(context: Context?, attrs: AttributeSet?) : super(context, attrs)
constructor(context: Context?, attrs: AttributeSet?, defStyleAttr: Int) : super(context, attrs, defStyleAttr)
constructor(context: Context?, attrs: AttributeSet?, defStyleAttr: Int, defStyleRes: Int) : super(context, attrs, defStyleAttr, defStyleRes)
override fun onMeasure(widthMeasureSpec: Int, heightMeasureSpec: Int) {
super.onMeasure(widthMeasureSpec, heightMeasureSpec)
setMeasuredDimension(MeasureSpec.makeMeasureSpec(bitmap.width, MeasureSpec.EXACTLY), MeasureSpec.makeMeasureSpec(bitmap.height, MeasureSpec.EXACTLY))
}
override fun onDraw(canvas: Canvas) {
val shaderA: Shader = LinearGradient(0F, 0F, 0F, bitmap.height.toFloat(), -0x1, 0x00ffffff, Shader.TileMode.CLAMP)
val shaderB: Shader = BitmapShader(bitmap, Shader.TileMode.CLAMP, Shader.TileMode.CLAMP)
paint.shader = ComposeShader(shaderA, shaderB, PorterDuff.Mode.SRC_IN)
canvas.drawRect(0f, 0f, bitmap.width.toFloat(), bitmap.height.toFloat(), paint)
}
init {
bitmap = BitmapFactory.decodeResource(context.resources, R.drawable.ktor)
}
}
The key is to leverage ComposeShader with LinearGradient and BitmapShader.
reference:
Android extends imageView gradient transparent vertical linear from input bitmap
Drawing a Bitmap to a Canvas with an alpha gradient
Related
In Android UI, we can create a custom view by overloading View as shown below.
class CustomView #JvmOverloads constructor(
context: Context,
attrs: AttributeSet? = null,
defStyleAttr: Int = 0)
: View(context, attrs, defStyleAttr) {
override fun onDraw(canvas: Canvas) {
super.onDraw(canvas)
// Perform the needing drawing
if (isAttachedToWindow) invalidate()
}
override fun onMeasure(widthMeasureSpec: Int, heightMeasureSpec: Int) {
super.onMeasure(widthMeasureSpec, heightMeasureSpec)
val desiredWidth = suggestedMinimumWidth + paddingLeft + paddingRight
val desiredHeight = suggestedMinimumHeight + paddingTop + paddingBottom
setMeasuredDimension(View.resolveSize(desiredWidth, widthMeasureSpec),
View.resolveSize(desiredHeight, heightMeasureSpec))
}
}
Can we wrap JetpackCompose in this CustomView, so that the underlying Drawing it using JetpackCompose instead?
I check https://developer.android.com/jetpack/compose/interop/interop-apis, don't seems to have it stated.
To get it to work, we can have
class CustomComposeView #JvmOverloads constructor(
context: Context,
attrs: AttributeSet? = null,
defStyleAttr: Int = 0
) : AbstractComposeView(context, attrs, defStyleAttr) {
#Composable
override fun Content() {
// JetpacCompose code here
}
}
In the XML, we can have something like this
<com.package.CustomComposeView
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:minHeight="700dp" />
I have a Custom EditTextClass that Im using for all EditTexts on my App -
Class CustomEditText : EditText {
...
}
I want to change the background image, so that it should be reflected in all the usages of this CustomEditText.
I tried this -
override fun onDraw(canvas: Canvas ? ) {
val d = AppCompatResources.getDrawable(mContext!!, R.drawable.new_drawable)
d?.draw(canvas!!)
super.onDraw(canvas)
}
and this -
fun init(context: Context ? , attrs : AttributeSet ? ) {
background = ResourcesCompat.getDrawable(context!!.getResources(), R.drawable.new_drawable, null)
setBackground(background)
}
Both the ways, don't work. Can anyone tell me what is the correct solution ?
You need to setBounds to drawable when draw drawable.
So you should make your draw method like this.
override fun onDraw(canvas: Canvas? ) {
val d = AppCompatResources.getDrawable(context!!, R.drawable.new_drawable)
d?.setBounds(0, 0, width, height)
d?.draw(canvas!!)
super.onDraw(canvas)
}
First draw to canvas and pass the canvas to parent meaning super.onDraw should be at bottom
override fun onDraw(canvas: Canvas ? ) {
val d = AppCompatResources.getDrawable(mContext!!, R.drawable.new_drawable)
d?.draw(canvas!!)
super.onDraw(canvas)
}
In my opinion, you should not override onDraw method as this could cause issues when super.onDraw(canvas) is called before your operations (your drawable could cover the text, hits, drawable elements, etc.).
I've made a demo, which worked for me:
class CustomEditText : AppCompatEditText {
constructor(context: Context) : super(context) {
init()
}
constructor(context: Context, attrs: AttributeSet?) : super(context, attrs) {
init()
}
constructor(context: Context, attrs: AttributeSet?, defStyleAttr: Int) : super(context,
attrs,
defStyleAttr) {
init()
}
private fun init() {
setBackgroundResource(R.drawable.new_drawable)
}
}
I'm trying to create an overlay for the corners in my app.
I want them to be rounded and black all the time like this:
I created it with this code:
<?xml version="1.0" encoding="utf-8"?>
<item>
<shape android:shape="rectangle">
<solid android:color="#000"/>
</shape>
</item>
<item>
<shape android:shape="rectangle">
<solid android:color="#FFF"/>
<corners android:radius="24dp"/>
</shape>
</item>
Then I added it to my theme as an android:windowFrame:
<style name="AppTheme" parent="Theme.AppCompat.Light.DarkActionBar">
<item name="android:windowFrame">#drawable/corner_overlay</item>
</style>
But obviosly everything was white then. Now I'm trying to clip the second shape instead of coloring it white and if that doesnt work I'm looking for another way to get my corner overlay working!
Thanks for every help!
You can create a Custom Frame Layout and wrap all your content with it.
class RoundedCornerLayout : FrameLayout {
private var paint: Paint? = null
private var paint2: Paint? = null
private var cornerRadius: Float = 10f
private var mWidth: Int = 0
private var mHeight: Int = 0
constructor(context: Context) : super(context) {
init(context, null, 0)
}
constructor(context: Context, attrs: AttributeSet) : super(context, attrs) {
init(context, attrs, 0)
}
constructor(context: Context, attrs: AttributeSet, defStyle: Int) : super(context, attrs, defStyle) {
init(context, attrs, defStyle)
}
private fun init(context: Context, attrs: AttributeSet?, defStyle: Int) {
val metrics = context.getResources().getDisplayMetrics()
cornerRadius = TypedValue.applyDimension(TypedValue.COMPLEX_UNIT_DIP, CORNER_RADIUS, metrics)
paint = Paint(Paint.ANTI_ALIAS_FLAG)
paint?.color = Color.BLACK
paint2 = Paint(Paint.ANTI_ALIAS_FLAG)
paint2?.color = Color.TRANSPARENT
paint2?.xfermode = PorterDuffXfermode(PorterDuff.Mode.CLEAR)
setWillNotDraw(false)
setLayerType(LAYER_TYPE_SOFTWARE, null)
}
override fun onSizeChanged(w: Int, h: Int, oldw: Int, oldh: Int) {
super.onSizeChanged(w, h, oldw, oldh)
mWidth = w
mHeight = h
}
override fun onDraw(canvas: Canvas?) {
canvas?.drawRect(0f, 0f, mWidth.toFloat(), mHeight.toFloat(), paint)
canvas?.drawRoundRect(RectF(0f, 0f, mWidth.toFloat(), mHeight.toFloat()), cornerRadius, cornerRadius, paint2)
}
companion object {
private val CORNER_RADIUS = 40.0f
}
}
And use it in your xml:
<com.dantes.backstack.RoundedCornerLayout
android:id="#+id/wrapper"
android:layout_width="match_parent"
android:layout_height="match_parent">
<!-- Your content here-->
</com.dantes.backstack.RoundedCornerLayout>
Of course this is draft and you need to optimize it and make more clear to your purposes. But principal part is PorterDuffXfermode(PorterDuff.Mode.CLEAR). This mode make magic and cuts the tarnsparent rectangle from the dark.
Maybe it will help or at least push your minds in right direction.
I have a custom layout as below
class CustomComponent : FrameLayout {
constructor(context: Context?) : super(context)
constructor(context: Context?, attrs: AttributeSet?) : super(context, attrs) {
initAttrs(attrs)
}
constructor(context: Context?, attrs: AttributeSet?, defStyleAttr: Int) : super(context, attrs, defStyleAttr) {
initAttrs(attrs)
}
constructor(context: Context?, attrs: AttributeSet?, defStyleAttr: Int, defStyleRes: Int) : super(context, attrs, defStyleAttr, defStyleRes) {
initAttrs(attrs)
}
init {
LayoutInflater.from(context).inflate(R.layout.view_custom_component, this, true)
}
fun initAttrs(attrs: AttributeSet?) {
val typedArray = context.obtainStyledAttributes(attrs, R.styleable.custom_component_attributes, 0, 0)
my_title.text = resources.getText(typedArray
.getResourceId(R.styleable.custom_component_attributes_custom_component_title, R.string.component_one))
typedArray.recycle()
}
}
Now for each constructor, I have to explicitly call initAttrs(attrs) as I can't find way to access attrs in my init function.
Is there a way I could access attrs in init function, so I could call initAttrs(attrs) from init instead of having to explicitly call it in each of the constructor?
Use a constructor with default arguments:
class CustomComponent #JvmOverloads constructor(
context: Context,
attrs: AttributeSet? = null,
defStyle: Int = 0
) : FrameLayout(context, attrs, defStyle) {
fun init {
// Initialize your view
}
}
The #JvmOverloads annotation tells Kotlin to generate three overloaded constructors so they can be called in Java as well.
In your init function, attrs becomes available as a nullable type:
fun init {
LayoutInflater.from(context).inflate(R.layout.view_custom_component, this, true)
attrs?.let {
val typedArray = context.obtainStyledAttributes(it, R.styleable.custom_component_attributes, 0, 0)
my_title.text = resources.getText(typedArray
.getResourceId(R.styleable.custom_component_attributes_custom_component_title, R.string.component_one))
typedArray.recycle()
}
}
Note the usage of it in the let block.
I am extending a RelativeLayout to make a fragment decorator.
Like this:
<br.com.simplepass.mapfragmentwrapper.MapFragmentWrapper
android:id="#+id/map_wrapper"
android:layout_width="match_parent"
android:layout_height="match_parent">
<fragment
android:id="#+id/main_fragment_map"
android:layout_width="match_parent"
android:layout_height="match_parent"
class="com.google.android.gms.maps.MapFragment"/>
</br.com.simplepass.mapfragmentwrapper.MapFragmentWrapper>
Well, I would like to add some images in the RelativeLayout so it always get on top of the fragment, so I did this:
class MapFragmentWrapper : RelativeLayout {
var mMarkImageView : ImageView? = null
var mShadowView : View? = null
constructor(context: Context) : super(context) {
init(context)
}
constructor(context: Context, attrs: AttributeSet) : super(context, attrs) {
init(context)
}
constructor(context: Context, attrs: AttributeSet, defStyleAttr: Int) : super(context, attrs, defStyleAttr) {
init(context)
}
#TargetApi(23)
constructor(context: Context, attrs: AttributeSet, defStyleAttr: Int, defStyleRes: Int) : super(context, attrs, defStyleAttr, defStyleRes) {
init(context)
}
private fun init(context: Context) {
val params = RelativeLayout.LayoutParams(ViewGroup.LayoutParams.WRAP_CONTENT,
ViewGroup.LayoutParams.WRAP_CONTENT)
params.addRule(RelativeLayout.CENTER_IN_PARENT, RelativeLayout.TRUE);
mMarkImageView = ImageView(context)
mMarkImageView?.setImageResource(R.drawable.ic_marker_centered)
mShadowView = View(context)
mShadowView?.setBackgroundResource(R.drawable.map_pin_shadow)
addView(mMarkImageView, params)
addView(mShadowView, params)
}
[some more code here...]
}
But, when my activity starts, the fragment gets on top of my added views (mMarkImageView and mShadowView)... and that's exactly the opposite of what a want.
So how do I programmatically put the views at the last position in viewgroups?
Any help is appreciated!
So I found the answer:
override fun onLayout(changed: Boolean, l: Int, t: Int, r: Int, b: Int) {
super.onLayout(changed, l, t, r, b)
removeView(mMarkImageView)
removeView(mShadowView)
addView(mMarkImageView, -1 , params)
addView(mShadowView, -1, params)
}
doing this I put the layouts at the end of the viewgroup