Kotlin: Styling Text in Snackbar with SDK later than 28 - android

I am working on a Kotlin project and I wish to style the text in a Snackbar, specifically the text font. I have been to many websites that address this problem, but they all use this line in the Snackbar body:
val snackbarTextView = snackbar.view.findViewById(android.support.design.R.id.snackbar_text) as TextView
The problem setting the TextView with this code, is that it only works if your project targets SDK 28 or older, and legacy libraries are enabled. I am targeting SDK 30 and then the element 'design' in that line is always an unresolved reference.
My Snackbar code is as follows:
private fun showSnackbar(message: String)
{
val coordinatorLayout: CoordinatorLayout = findViewById(R.id.coordinatorLayout)
val snackbar = Snackbar.make(coordinatorLayout, message, Snackbar.LENGTH_INDEFINITE)
snackbar.setBackgroundTint(Color.CYAN)
snackbar.setAction("DISMISS", View.OnClickListener {
// executed when DISMISS is clicked
})
snackbar.setTextColor(Color.BLACK)
snackbar.show()
}
I can set the text color and size, but not other attributes like the font. How can I do this in Kotlin with the later SDKs? Thanks!

from here
suppose you have your custom font inside res>font>myfont.ttf
create this class:
class CustomTypefaceSpan(var typeface: Typeface) : MetricAffectingSpan() {
override fun updateDrawState(ds: TextPaint) {
applyCustomTypeFace(ds, typeface)
}
override fun updateMeasureState(paint: TextPaint) {
applyCustomTypeFace(paint, typeface)
}
private fun applyCustomTypeFace(paint: Paint, tf: Typeface) {
paint.typeface = tf
}
}
then :
private fun showSnackbar(message: String) {
val ss = SpannableStringBuilder(message)
val font = ResourcesCompat.getFont(this, R.font.myfont)
ss.setSpan(CustomTypefaceSpan(font!!), 0, message.length, Spanned.SPAN_EXCLUSIVE_INCLUSIVE)
val coordinatorLayout: CoordinatorLayout = findViewById(R.id.coordinatorLayout)
val snackbar = Snackbar.make(coordinatorLayout, ss, Snackbar.LENGTH_INDEFINITE)
snackbar.setBackgroundTint(Color.CYAN)
snackbar.setAction("DISMISS", View.OnClickListener {
// executed when DISMISS is clicked
})
snackbar.setTextColor(Color.BLACK)
snackbar.show()
}

Related

Issue with custom dialog after removing Kotlin extension from the Android project

I have a custom dialog in my android project and it was working fine. After I removed kotlin extension from the project, I have modified my code as follows but there is some issue with the Views in the custom dialog. Codes etTitle.visibility = View.GONE and val newRequest = etDetail.text.toString() didn't work as I expected. It didn't hide the view etTitle and the value in the EditText etDetail is not picked also, it always returns emplty even when there is some value.
private lateinit var bindingDialogLayout: CustomDialogBinding
fun specialRequestDialog(currentRequest: String?) {
bindingDialogLayout = CustomDialogBinding.inflate(layoutInflater)
val dialogLayout = layoutInflater.inflate(R.layout.custom_dialog, null)
val etTitle = bindingDialogLayout.etTitle
val etDetail = bindingDialogLayout.etDetails
etTitle.visibility = View.GONE
etDetail.setText(currentRequest)
MaterialAlertDialogBuilder(this)
.setTitle("What is your special request?")
.setCancelable(false)
.setPositiveButton("Save") { dialog, which ->
val newRequest = etDetail.text.toString()
if (newRequest.isEmpty()) {
showErrorSnackBar("Type in if you have any special request, else hit cancel", true)
} else {
addButton.visibility = View.GONE
deleteButton.visibility = View.VISIBLE
}
}
.setNegativeButton("Cancel") { dialog, which ->
dialog?.dismiss()
}
.setView(dialogLayout)
.show()
}
You set the wrong view to the dialog.
Use this instead:
.setView(bindingDialogLayout.root)

Custom Rating Bar using Anko

I've been exploring android development using Anko and Kotlin and had some trouble with the ratingbar, namely it size. I've tried to make it smaller using a custom style but themedRatingBar doesn't seem to work. So I've opted to make a custom ratingbar instead. I can't seem to make it work that way I want it to in that when I set it this way in the main activity:
starRatingView{
setRating(3)
}
It does not output a rating of 3 and instead will output the default rating, which is zero.
class StarRatingView: _LinearLayout {
lateinit var imageViewStars: List<ImageView>
private var starNum: Float = 0f
private var starSize: Int = 5
constructor(context: Context): super(context) {
initializeView()
}
fun initializeView() {
with(this) {
linearLayout {
relativeLayout {
linearLayout {
for (i in 1..starSize)
imageView(R.drawable.ratingbar_empty)
}
linearLayout {
for (i in 0..Math.round(starNum)) {
imageView(R.drawable.ratingbar_filled)
}
}
}
}
}
}
fun setSize(starSize: Int){
this.starSize = starSize
}
fun setRating(starNum: Float){
this.starNum = starNum
}
}
Above is the code that I use to create the custom RatinBar. Trying to avoid using XMLs as much as possible and use Anko instead.
if I can help here is a possible solution to show a custom RatingBar in a custom Alert using Anko library and Kotlin code...
var rateGave: String? = null
alert {
title = "Rate your experience"
customView {
linearLayout {
ratingBar {
numStars = 5 //here is to define the number of stars you want
rating = 4f //starting rate to show as the alert pop up
setOnRatingBarChangeListener { ratingBar, rating, fromUser ->
rateGave = rating.toString()
}
}
}
}
positiveButton("Rate") { rate() }
}.show()
}
fun rate() {
println(rateGave) //now it's just printing out the rate to show that it's working fine, but in this function you can mange all the operations you need with the rating value
}
Hope it can be usefull.
Have a nice day :)

How to group the duplicated statement in .apply in Kotlin?

Here is my code. As you can see the body of apply is exactly the same. Is there a better than use extension function?
contentText?.let {
contentTextView?.apply {
visibility = View.VISIBLE
text = contentText
}
}
titleText?.let {
titleTextView?.apply {
visibility = View.VISIBLE
text = titleText
}
}
Here is my function
private fun setTextAndVisiblity(textView: TextView?, newText: String?): TextView? {
return textView?.apply {
visibility = View.VISIBLE
text = newText
}
}
This is my code when apply function
contentText?.let {
setVisibleText(contentTextView, it)
}
titleText?.let {
setVisibleText(titleTextView, it)
}
I would write the extension function like this:
fun TextView.setVisibleIfTextNotNull(text: CharSequence?) = text?.let {
visibility = View.VISIBLE
this.text = it
}
Usage:
contentTextView?.setVisibleIfTextNotNull(contentText)
titleTextView?.setVisibleIfTextNotNull(titleText)
You can either make it as an nested function or private extension function as you like. The name of the function may not be clear enough to clarify what the function does, you may think of a better one.
An extension function seems like the best choice. If you make the function return this you can use it without apply.
Your other choice would be to create an ordinary function and pass it into also using method references, e.g.
fun setVisibleText(view: View) { }
titleTextView.also(this::setVisibleText)
the cleanest for me is to declare an extension function as:
fun TextView.setVisibleWithText(text: String?){
text ?: return
visibility = View.VISIBLE
setText(text)
}
then calling it as:
myTextView?.setVisibleWithText(myText)
Anyway,remember that an extension functions it just an static util function.
This function below:
fun TextView.setVisibleWithText(text: String){
visibility = View.VISIBLE
setText(text)
}
Will become something like this in java:
class TextViewKt {
public static function setVisibleWithText(#NotNull TextView receiver, #NotNull String text){
receiver.visibility = View.VISIBLE
receiver.setText(text)
}
}
And after you can call it as:
theText?.let { theTextView?.setVisibleWithText(it) }
You can always declare a normal funtion as:
fun setVisibleWithText(textView: TextView, text: String){
textView.visibility = View.VISIBLE
textView.text = text
}
or if you want to make the check inside:
/***
* It makes the textview visible if text is not null (it will stay visible if it was visible before)
**/
fun setVisibleWithText(textView: TextView, text: String?){
text ?: return
textView.visibility = View.VISIBLE
textView.text = text
}

How to reference other views in Anko DSL?

I'm using Anko in my Android project, but I don't know how can it reference the child views I created in the DSL when the referenced view is not at the same level where I reference it.
The following code works:
alert {
customView {
val input = textInputLayout {
editText {
hint = "Name"
textColor =resources.getColor(R.color.highlight)
}
}
positiveButton("OK") { "${input.editText.text}" }
}
}.show()
but the following code does not work:
alert {
customView {
val vertical = verticalLayout {
textView {
text = "Edit device name"
textColor = resources.getColor(R.color.highlight)
textSize = 24F
}
val input = textInputLayout {
editText {
hint = "Name"
textColor = resources.getColor(R.color.highlight)
}
}
}
positiveButton("OK") { "${vertical.input.editText.text}" } // Cannot resolve "input"
}
}.show()
As I see it there are two ways. The super hacky way would be to declare the positive button within the textInputLayout block. This is possible because you can access all outer scopes from within any nested scope and the positiveButton method is declared in the alert scope:
alert {
customView {
verticalLayout {
textInputLayout {
val editText = editText {
hint = "Name"
}
positiveButton("OK") { toast("${editText.text}") }
}
}
}
}.show()
The less hacky way would be to declare a variable that can be accessed from both scopes. However you would need to make it nullable since you can't initialize it immediately:
alert {
var editText: EditText? = null
customView {
verticalLayout {
textInputLayout {
editText = editText {
hint = "Name"
}
}
}
}
positiveButton("OK") { toast("${editText!!.text}") }
}.show()
I propose using findViewById()
alert {
customView {
val vertical = verticalLayout {
textView {
text = "Edit device name"
textSize = 24F
}
val input = textInputLayout {
editText {
id = R.id.my_id_resource // put your id here
hint = "Name"
}
}
}
positiveButton("OK") { "${(vertical.findViewById(R.id.my_id_resource) as? EditText)?.text}" }
}
}.show()
You can always elevate a view, passing the context vertical manually:
customView {
val vertical = verticalLayout {
textView {
text = "Edit device name"
textColor = resources.getColor(R.color.highlight)
textSize = 24F
}
}
val input = /*here:*/ vertical.textInputLayout {
editText {
hint = "Name"
textColor = resources.getColor(R.color.highlight)
}
}
positiveButton("OK") { "${input.editText.text}" }
}
I usually declare a view as a property in a class with lateinit modifier; this way it's not nullable and most of views are declared in one place, improving readability:
lateinit var toolbar: Toolbar
...
appBarLayout {
toolbar = toolbar {}.lparams(width = matchParent, height = matchParent)
}.lparams(width = matchParent)
...
setSupportActionBar(toolbar)
Probably the best way is to use Android IDs for the elements you need to reference later, and the find<T : View>(Int) : T function. This allows you to reference them from anywhere, as long as the view still exists, and you have access to the application/activity scope.
See the Anko Documentation for details
Example case: Dynamically adding buttons to an existing view
verticalLayout {
id = R.id.button_container
}
//note that the code below here may be executed anywhere after the above in your onCreate function
//verticalLayout is a Anko subclass of LinearLayout, so using the android class is valid.
val buttonContainer = find<LinearLayout>(R.id.button_container)
val containerContext = AnkoContext.Companion.create(ctx, buttonContainer)
val button = ctx.button {
text = "click me"
onClick = { toast("created with IDs!") }
}
buttonContainer.addView(button.createView(containerContext, buttonContainer))

Change Toast Font

Currently, I'm trying to develop an app.
and I don't know how to change the Toast font. .
final OnClickListener clickListener = new OnClickListener() {
public void onClick(View v) {
try {
Toast.makeText(nova.this,"Hello", 500000).show();
}
catch (Exception e) {
Toast.makeText(nova.this,"Exception:" +e, 500000);
}
}
};
I want to change the text "Hello" with custom font I've tried with TypeFace.
and Then, I want to set a variable at the place "TextClicked" .. I've tried with a local variable .. but it doesn't work
any help with example source code will be really great for me.
The answer is here: https://stackoverflow.com/a/13231981
After refactoring a little:
Toast toast = Toast.makeText(context, R.string.yummyToast, Toast.LENGTH_SHORT);
LinearLayout toastLayout = (LinearLayout) toast.getView();
TextView toastTV = (TextView) toastLayout.getChildAt(0);
toastTV.setTextSize(30);
toast.show();
This worked for me like a charm!
From the official documentation:
Create your custom ToastView
If a simple text message isn't enough, you can create a customized layout for your toast notification. To create a custom layout, define a View layout, in XML or in your application code, and pass the root View object to the setView(View) method.
Following the link to the official Google Documentation will provide examples.
You can use a SpannableString to set the font:
Typeface font = Typeface.createFromAsset(getAssets(), "fonts/ATaha.ttf");
SpannableString efr = new SpannableString("Toast font changed!");
efr.setSpan(new TypefaceSpan(font), 0, efr.length(), Spannable.SPAN_EXCLUSIVE_EXCLUSIVE);
Toast.makeText(this, efr, Toast.LENGTH_SHORT).show();
A custom Span class that has a specific Typeface set:
public class TypefaceSpan extends MetricAffectingSpan {
private Typeface mTypeface;
public TypefaceSpan(Typeface typeface) {
mTypeface = typeface;
}
#Override
public void updateMeasureState(TextPaint p) {
p.setTypeface(mTypeface);
p.setFlags(p.getFlags() | Paint.SUBPIXEL_TEXT_FLAG);
}
#Override
public void updateDrawState(TextPaint tp) {
tp.setTypeface(mTypeface);
tp.setFlags(tp.getFlags() | Paint.SUBPIXEL_TEXT_FLAG);
}
}
Unfortunately the code on the Java page is bugged. Here is a link to a working function you can implement that gives you the text (I know, because I tested it), and with a little ingenuity, could be expanded to pass arguments for size, color, etc...
Toast Font size function here
Kotlin function:
fun makeLargeTextToast(text: CharSequence): Toast {
return Toast.makeText(applicationContext, text, Toast.LENGTH_LONG).also {
val toastLayout = it.view as LinearLayout
val toastTV = toastLayout.getChildAt(0) as TextView
toastTV.textSize = 30f
}
}
Use it as:
makeLargeTextToast("text message").show()
I used this solution in kotlin
in CustomView or Fragment
fun persianToast(message: String): Toast {
return Toast.makeText(context, message, Toast.LENGTH_SHORT).also {
val view = it.view as LinearLayout
val tv = view.getChildAt(0) as TextView
val typeFace = Typeface.createFromAsset(context?.assets, MyApplication.getFont(MyApplication.LIGHT_FONT))
tv.typeface = typeFace
}
}
MyApplication class :
class MyApplication : Application() {
companion object {
const val NORMAL_FONT = 0
const val BOLD_FONT = 1
const val MEDIUM_FONT = 2
const val LIGHT_FONT = 3
const val ULTRA_LIGHT_FONT = 4
#JvmStatic
fun getFont(type: Int): String {
return when (type) {
LIGHT_FONT -> "font/fontLight.ttf"
BOLD_FONT -> "font/fontBold.ttf"
MEDIUM_FONT -> "font/fontMedium.ttf"
ULTRA_LIGHT_FONT -> "font/fontUltraLight.ttf"
else -> "font/fontNormal.ttf"
}
}
}
used in fragment:
override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
Toast(context)
persianToast("javid sattar").show()
}
good luck!!

Categories

Resources