In my application I create one custom view for show TextView and FontAwesome!
I write below codes, but not set values and just show default values!
TextWithIcon class :
class TextWithIcon #JvmOverloads constructor(
context: Context,
attrs: AttributeSet? = null,
defStyle: Int = 0,
defStyleRes: Int = 0
) : LinearLayout(context, attrs, defStyle, defStyleRes) {
init {
LayoutInflater.from(context).inflate(R.layout.layout_text_with_awesome, this, true)
orientation = VERTICAL
attrs?.let {
val typedArray = context.obtainStyledAttributes(R.styleable.TextWithIcon)
val title = resources.getText(
typedArray.getResourceId(
R.styleable.TextWithIcon_customText,
R.string.app_name
)
)
val icon = resources.getText(
typedArray.getResourceId(
R.styleable.TextWithIcon_customIcon,
R.string.app_name
)
)
val titleTxt = getChildAt(0) as TextView
titleTxt.text = title
Log.e("titleTxt",title.toString())
//binding.iconTxt.text = "&#x$icon"
typedArray.recycle()
}
}
}
CustomViewLayout file :
<?xml version="1.0" encoding="utf-8"?>
<merge xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:app="http://schemas.android.com/apk/res-auto">
<TextView
android:id="#+id/titleTxt"
android:layout_width="wrap_content"
android:layout_height="#dimen/_20mdp"
android:gravity="right"
android:textColor="#color/darkJungleGreen"
android:textSize="#dimen/_10font_mdp" />
<com.myapp.utils.views.FontAwesome
android:id="#+id/iconTxt"
android:layout_width="#dimen/_20mdp"
android:layout_height="#dimen/_20mdp"
android:layout_gravity="right"
android:gravity="center"
android:textColor="#color/beauBlue"
android:textSize="#dimen/_10font_mdp"
app:fontPath="fonts/fontawesome_re.ttf" />
</merge>
And I used this class with code in XML :
<com.myapp.TextWithIcon
android:id="#+id/item1Title"
android:layout_width="0dp"
android:layout_height="wrap_content"
android:layout_marginRight="#dimen/_10mdp"
android:drawablePadding="#dimen/_5mdp"
android:gravity="center"
android:textColor="#color/ochre"
android:textSize="#dimen/_10font_mdp"
app:customIcon="f007"
app:customText="wegwergewrg"
app:fontPath="fonts/iransans_bold.ttf"
app:layout_constraintEnd_toStartOf="#id/item1Line"
app:layout_constraintTop_toTopOf="#id/item1Line" />
I set value with this code : app:customText="wegwergewrg", but not show this and just show default value from
resources.getText(
typedArray.getResourceId(
R.styleable.TextWithIcon_customText,
R.string.app_name
)
)
How can I fix it?
You are going to want your styleable to look something like this:
<resources>
<declare-styleable name="TextWithIcon">
<attr name="customText" format="reference|string" />
</declare-styleable>
</resources>
This definition will permit you to use text as well as a string resource id for "app:customText".
You can get the value from the attribute as follows:
class TestCustomView #JvmOverloads constructor(
context: Context, attrs: AttributeSet? = null
) : androidx.appcompat.widget.AppCompatTextView(context, attrs) {
init {
val a = context.obtainStyledAttributes(attrs, R.styleable.TextWithIcon)
for (attr in 0 until a.indexCount) {
when (a.getIndex(attr)) {
R.styleable.TextWithIcon_customText -> {
text = a.getText(attr) ?: "Unknown text."
}
}
}
a.recycle()
}
}
Related
I have a Composable wrapped in AbstractComposeView to be used in XML.
How to set values to this view.
Example,
Composable and AbstractComposeView
class MyComposeView #JvmOverloads constructor(
context: Context,
attrs: AttributeSet? = null,
defStyleAttr: Int = 0,
) : AbstractComposeView(context, attrs, defStyleAttr) {
private var titleText by mutableStateOf("Default text")
var titleValue: String
get() = titleText
set(value) {
titleText = value
}
#Composable
override fun Content() {
ListItem(
text = titleText,
)
}
}
#Composable
fun ListItem(
text: String,
) {
Row(
modifier = Modifier,
) {
Text(
text = text,
)
}
}
XML
<androidx.constraintlayout.widget.ConstraintLayout xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:app="http://schemas.android.com/apk/res-auto"
xmlns:tools="http://schemas.android.com/tools"
android:layout_width="match_parent"
android:layout_height="match_parent"
tools:context=".ViewActivity">
<com.example.android.MyComposeView
android:id="#+id/my_view"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
app:layout_constraintBottom_toBottomOf="parent"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toTopOf="parent" />
</androidx.constraintlayout.widget.ConstraintLayout>
Activity
findViewById<MyComposeView>(R.id.my_view).titleValue = "From Activity"
This gives the intended result, but how to achieve the same without any code changes in the activity?
Something like app:titleText? (Tried this didn't work).
As for a regular custom view, you will need to rely on attribut set.
First create it in res > values > attrs.xml
Somethings like:
<resources>
<declare-styleable name="MyComposeView">
<attr name="titleText" format="string" />
</declare-styleable>
</resources>
Then apply it in your MyComposeView class
init {
context.theme.obtainStyledAttributes(
attrs,
R.styleable.MyComposeView,
0, 0).apply {
try {
titleText = attributes.getString(R.styleable.MyComposeView_ titleText).toString()
} finally {
recycle()
}
}
}
Références:
https://developer.android.com/develop/ui/views/layout/custom-views/create-view#customattr
https://developer.android.com/develop/ui/views/layout/custom-views/create-view#applyattr
I have custom LoadingButton class implemented as FrameLayout which is then used inside XMLs as component. Its basically Button but made completely custom with its own layout and components.
What I want is to add shadow there (elevation, translationZ) but this shadow is clipped everywhere.
I want to have this button dynamic that I can adjust its margins or change its shape like adding static width and height on different screens without distorting shadow around. Shadow is clipped either from top or bottom all the time.
Example of xml view:
<?xml version="1.0" encoding="utf-8"?>
<layout xmlns:android="http://schemas.android.com/apk/res/android">
<data>
<variable name="CustomRes" type="com.project.utils.CustomResources"/>
</data>
<merge
android:duplicateParentState="true"
android:layout_width="wrap_content"
android:layout_height="wrap_content">
<LinearLayout
android:id="#+id/buttonParent"
android:orientation="horizontal"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:layout_gravity="center"
android:duplicateParentState="true"
android:clipToPadding="false"
android:clipChildren="false"
android:gravity="center">
<ImageView
android:id="#+id/buttonIcon"
android:layout_width="wrap_content"
android:layout_height="match_parent"
android:adjustViewBounds="true"
android:layout_gravity="center"
android:scaleType="fitCenter"/>
<TextView
android:id="#+id/buttonText"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:duplicateParentState="true"
android:textSize="#dimen/text_medium"
android:textStyle="bold"
android:lines="1"
android:layout_gravity="center"/>
</LinearLayout>
<com.project.components.loading_indicator.LoadingIndicator
android:id="#+id/progress"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_gravity="center"
android:gravity="center" />
</merge>
</layout>
Class:
#Suppress("DEPRECATION")
class LoadingButton : FrameLayout {
#StyleableRes
internal val btnIcoIndex = 0
#SuppressLint("ResourceType")
#StyleableRes
internal val btnTextResIndex = 1
#StyleableRes
#SuppressLint("ResourceType")
internal val btnTextIndex = 2
#StyleableRes
#SuppressLint("ResourceType")
internal val btnTextSizeIndex = 3
private val buttonParent: LinearLayout
private val progressBar: LoadingIndicator
private val buttonIcon: ImageView
private val buttonText: TextView
private var buttonTextVal: String? = null
init {
CustomResources.inflateLayout(LayoutInflater.from(context), R.layout.loading_button, this)
buttonParent = findViewById(R.id.buttonParent)
buttonIcon = findViewById(R.id.buttonIcon)
buttonText = findViewById(R.id.buttonText)
progressBar = findViewById(R.id.progress)
buttonText.addOnLayoutChangeListener { v, _, _, _, _, _, _, _, _ ->
textViewInitWidth = maxOf(v.measuredWidth, textViewInitWidth)
}
}
#SuppressLint("ClickableViewAccessibility")
constructor(context: Context) : super(context) {
parseAttrs(context)
}
#SuppressLint("ClickableViewAccessibility")
constructor(context: Context, attrs: AttributeSet) : super(context, attrs) {
parseAttrs(context, attrs)
}
#SuppressLint("ClickableViewAccessibility")
constructor(context: Context, attrs: AttributeSet, defStyleAttr: Int) : super(
context,
attrs,
defStyleAttr
) {
parseAttrs(context, attrs, defStyleAttr)
}
#SuppressLint("ResourceType")
private fun parseAttrs(context: Context, attrs: AttributeSet? = null, defStyleAttr: Int? = null) {
//Load from custom attributes
setLoading(false)
val sets = intArrayOf(R.attr.l_buttonIcon, R.attr.l_buttonTextId, R.attr.l_buttonText, R.attr.l_buttonTextSize)
if (attrs != null){
val typedArray = context.obtainStyledAttributes(attrs, sets)
val buttonIco = typedArray.getResourceId(btnIcoIndex, 0)
val buttonTxtRes = typedArray.getText(btnTextResIndex)?.let { res->
App.getString(res as String).toLowerCase(Locale.getDefault()).replaceFirstChar { it.toUpperCase() }
}
val buttonTxtRaw = typedArray.getText(btnTextIndex)?.let { it as String }?:"null"
val buttonTxtSize = typedArray.getDimension(btnTextSizeIndex, resources.getDimension(R.dimen.text_medium))
val buttonStyle = attrs.styleAttribute
App.log("RawTextButton: $buttonTxtRaw, hasValue: ${typedArray.getString(btnTextIndex)}")
setButtonTextSize(buttonTxtSize)
buttonTxtRes?.let { setButtonText(buttonTxtRes) }?:kotlin.run{ setButtonText(buttonTxtRaw) }
setButtonIcon(buttonIco)
buttonParent.gravity = Gravity.CENTER
buttonTextVal = buttonTxtRes?.let { buttonTxtRes.toString() }?:buttonTxtRaw
typedArray.recycle()
clipToPadding = false
clipChildren = false
when(buttonStyle){
R.style.button_primary -> {
App.log("BtnStyleId - primary")
setupTextStyle(buttonStyle)
setProgBarColor(R.color.button_light)
setIconTint(ContextCompat.getColor(context, R.color.button_light))
maybeSetAmbientShadow(R.color.button_primary)
}
R.style.button_secondary -> {
App.log("BtnStyleId - secondary")
setupTextStyle(buttonStyle)
setProgBarColor(R.color.button_primary)
setIconTint(ContextCompat.getColor(context, R.color.button_light))
maybeSetAmbientShadow(R.color.button_primary)
}
R.style.button_secondary_alert -> {
App.log("BtnStyleId - secondary alert")
setupTextStyle(buttonStyle)
setProgBarColor(R.color.button_invalid)
setIconTint(ContextCompat.getColor(context, R.color.button_invalid))
maybeSetAmbientShadow(R.color.button_invalid)
}
else -> App.log("BtnStyleId -> $buttonStyle")
}
} else {
App.log("BtnStyleId -> attrs==null")
}
}
#SuppressLint("ResourceType")
private fun setupTextStyle(buttonStyle: Int){
val attrs = intArrayOf(android.R.attr.textAppearance)
val typedAttrs = context.obtainStyledAttributes(buttonStyle, attrs)
val textAppearance = typedAttrs.getResourceId(0, 0)
TextViewCompat.setTextAppearance(buttonText, textAppearance)
typedAttrs.recycle()
}
private fun setProgBarColor(color: Int){
progressBar.setColorTint(color)
}
fun setIconTint(color: Int){
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.Q){
buttonIcon.colorFilter = BlendModeColorFilter(color, BlendMode.SRC_IN)
} else {
App.log("setting color filter")
buttonIcon.setColorFilter(color, PorterDuff.Mode.SRC_IN)
}
}
private fun maybeSetAmbientShadow(color: Int){
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.P){
outlineAmbientShadowColor = ContextCompat.getColor(this.context, color)
outlineSpotShadowColor = ContextCompat.getColor(this.context, color)
}
}
}
I need some variable to set which will set clipToPadding false and clipChildren false for every single screen where this button is implemented without rewriting 80+ xml files. Because some screens have marginTop set for this button but not marginBottom, and that will cause clipping even if I set those 2 parameters to false. Clearly bad design from Android for handling shadows.
Base style for primary and secondary button:
<style name="button" parent="#android:style/Widget.Material.Button">
<item name="android:layout_width">wrap_content</item>
<item name="android:layout_height">wrap_content</item>
<item name="android:minHeight">0dp</item>
<item name="android:minWidth">0dp</item>
<item name="android:paddingStart">#dimen/button_padding_start</item>
<item name="android:paddingEnd">#dimen/button_padding_end</item>
<item name="android:paddingTop">#dimen/button_padding_top</item>
<item name="android:paddingBottom">#dimen/button_padding_bottom</item>
<item name="android:elevation">4dp</item>
<item name="android:translationZ">4dp</item>
<item name="android:stateListAnimator">#null</item>
<item name="android:clipToPadding">false</item>
</style>
Usage:
<com.project.components.loading_button.LoadingButton
android:id="#+id/loginButton"
android:layout_gravity="center"
android:layout_weight="0.5"
android:layout_marginStart="8dp"
app:l_buttonTextId="button_login"
style="#style/button.primary" />
When I'm putting my TabLayout inside MaterialCardView to make TabLayout rounder but I'm not getting desired result
<?xml version="1.0" encoding="utf-8"?>
<androidx.constraintlayout.widget.ConstraintLayout
xmlns:android = "http://schemas.android.com/apk/res/android"
xmlns:app = "http://schemas.android.com/apk/res-auto"
android:layout_width = "match_parent"
android:layout_height = "match_parent">
<LinearLayout
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:orientation="vertical"
app:layout_constraintBottom_toBottomOf="parent"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toTopOf="parent">
<com.google.android.material.card.MaterialCardView
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_margin="12dp"
app:cardCornerRadius="#dimen/dp_20"
android:theme="#style/Theme.MaterialComponents.Light"
app:strokeColor="#color/dark_blue"
app:cardPreventCornerOverlap="true"
app:strokeWidth="1dp">
<com.google.android.material.tabs.TabLayout
android:id = "#+id/tab_layout"
android:layout_width = "match_parent"
android:layout_height = "#dimen/dp_35"
app:tabGravity = "fill"
app:tabIndicatorColor = "#color/dark_blue"
app:tabIndicatorGravity = "stretch"
app:tabMaxWidth = "0dp"
app:tabMode = "fixed"
app:tabSelectedTextColor = "#android:color/white"
app:tabTextAppearance = "#style/AppTabTextTools"
app:tabTextColor = "?attr/colorPrimary">
</com.google.android.material.tabs.TabLayout>
</com.google.android.material.card.MaterialCardView>
<androidx.viewpager.widget.ViewPager
android:id="#+id/viewpager"
android:layout_width="match_parent"
android:layout_height="match_parent"
app:layout_behavior="#string/appbar_scrolling_view_behavior"
/>
</LinearLayout>
</androidx.constraintlayout.widget.ConstraintLayout>
Please help me to figure out what's the problem in above layout.
I need result like below image
As suggested by #Malik Saifullah followed https://stackoverflow.com/a/50621395/770703
Created custom RoundedTabLayout class for simplification
class RoundedTabLayout : TabLayout, TabLayout.OnTabSelectedListener {
private lateinit var currentContext: Context
constructor(context: Context, attrs: AttributeSet, defStyle: Int) : super(context, attrs, defStyle) {
currentContext = context
addOnTabSelectedListener(this)
}
constructor(context: Context, attrs: AttributeSet) : super(context, attrs, 0) {
currentContext = context
addOnTabSelectedListener(this)
}
constructor(context: Context) : super(context)
override fun onTabSelected(tab: Tab?) {
if (selectedTabPosition == 0) {
setTabBG(R.drawable.tab_left_select, R.drawable.tab_right_unselect)
} else {
setTabBG(R.drawable.tab_left_unselect, R.drawable.tab_right_select)
}
}
override fun onTabUnselected(tab: Tab?) {
}
override fun onTabReselected(tab: Tab?) {
}
private fun setTabBG(tab1: Int, tab2: Int) {
val tabStrip = getChildAt(0) as ViewGroup
val tabView1 = tabStrip.getChildAt(0)
val tabView2 = tabStrip.getChildAt(1)
if (tabView1 != null) {
val paddingStart = tabView1.paddingStart
val paddingTop = tabView1.paddingTop
val paddingEnd = tabView1.paddingEnd
val paddingBottom = tabView1.paddingBottom
ViewCompat.setBackground(tabView1, AppCompatResources.getDrawable(tabView1.context, tab1))
ViewCompat.setPaddingRelative(tabView1, paddingStart, paddingTop, paddingEnd, paddingBottom)
}
if (tabView2 != null) {
val paddingStart = tabView2.paddingStart
val paddingTop = tabView2.paddingTop
val paddingEnd = tabView2.paddingEnd
val paddingBottom = tabView2.paddingBottom
ViewCompat.setBackground(tabView2, AppCompatResources.getDrawable(tabView2.context, tab2))
ViewCompat.setPaddingRelative(tabView2, paddingStart, paddingTop, paddingEnd, paddingBottom)
}
}
}
As the title says, I have implemented this simple custom textView. The idea would be to apply a style that I choose by passing it through xml.
The class I created, however, is never called, in fact in debug it never stops at the breakpoints I entered.
In particular, the class I am using is the following
class MyTextView : AppCompatTextView {
constructor(context: Context, attrs: AttributeSet?, defStyle: Int) : super(
context, attrs, defStyle
) {
init(attrs)
}
constructor(context: Context, attrs: AttributeSet?) : super(context, attrs) {
init(attrs)
}
constructor(context: Context) : super(context) {
init(null)
}
private fun init(attrs: AttributeSet?) {
if (attrs != null) {
val a = context.obtainStyledAttributes(attrs, R.styleable.MyTextView)
val fontName = a.getString(R.styleable.MyTextView_fontName)
if (fontName != null) {
val myTypeface = Typeface.createFromAsset(context.assets, "fonts/$fontName")
typeface = myTypeface
}
a.recycle()
}
}
override fun onDraw(canvas: Canvas?) {
super.onDraw(canvas)
this.text = "new text"
}
}
Here my xml:
<com.example.utils.widget.MyTextView
android:id="#+id/myText"
android:layout_width="wrap_content"
android:layout_height="200dp"
android:elevation="20dp"
android:text="MyText"
app:fontName="ultrabold"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toTopOf="parent" />
and here the attrs.xml
<?xml version="1.0" encoding="utf-8"?>
<resources>
<declare-styleable name="MyTextView">
<attr name="fontName" format="string"/>
</declare-styleable>
</resources>
All compile correctly but I can't see my textview and not invoked by breakpoints. Anyone know why?
I want to have my CustomTextInputLayout to have Widget.MaterialComponents.TextInputLayout.OutlinedBox as default style without defining it anywhere in the XML.
I tried this
class CustomTextInputLayout #JvmOverloads constructor(
context: Context, attrs: AttributeSet? = null, defStyleAttr: Int = 0
) : TextInputLayout(ContextThemeWrapper(context, R.style.Widget_MaterialComponents_TextInputLayout_OutlinedBox), attrs, defStyleAttr) {
}
and this
class CustomTextInputLayout #JvmOverloads constructor(
context: Context, attrs: AttributeSet? = null, defStyleAttr: Int = 0
) : TextInputLayout(context, attrs, R.style.Widget_MaterialComponents_TextInputLayout_OutlinedBox)
but it's not working. I've tried the default XML way
<com.custom.CustomTextInputLayout
style="#style/Widget.MaterialComponents.TextInputLayout.OutlinedBox"
...>
<com.google.android.material.textfield.TextInputEditText
...
android:hint="Sample Hint" />
</com.custom.CustomTextInputLayout>
and it's working.
What am I missing here?
How can I set a default style for custom TextInputLayout without using XML?
It is not exactly what you are looking for.
You can define:
public class CustomTextInputLayout #JvmOverloads constructor(
context: Context, attrs: AttributeSet? = null, defStyleAttr: Int = R.attr.textInputStyle
) : TextInputLayout(ContextThemeWrapper(context, R.style.Outlined_Theme), attrs, defStyleAttr) { ... }
with:
<style name="Outlined.Theme" parent="">
<item name="textInputStyle">#style/Widget.MaterialComponents.TextInputLayout.OutlinedBox</item>
</style>
Then in your layout juse use:
<com.example.quicksample.CustomTextInputLayout
....
android:hint="Sample">
<com.google.android.material.textfield.TextInputEditText../>
</com.example.quicksample.CustomTextInputLayout>
Your code doesn't work because ContextThemeWrapper(context, R.style.Widget_MaterialComponents_TextInputLayout_OutlinedBox) the 2nd parameter has to be an attribute theme and not a style (it is the same for TextInputLayout(context, attrs, R.style.Widget_MaterialComponents_TextInputLayout_OutlinedBox))