Android PreferenceScreen title in two lines - android

I have PreferenceScreen with long title especially in some languages. I'm able to set multiple lines title for CheckBoxPreference or ListPreference with this:
Android preference summary . How to set 3 lines in summary? , but how to set 2-lines title for PreferenceScreen? I can change style like here: How can I change font size in PreferenceScreen but this doesn't look perfect and it's inconsistent with preference style (font, size ...).
thanks!

By default the title is set to single line.
You need to extend Preference and get title textview and set single line to false.
Use this class instead of the regular PreferenceScreen:
public class TwoLinePreference extends Preference {
public TwoLinePreference(Context ctx, AttributeSet attrs, int defStyle) {
super(ctx, attrs, defStyle);
}
public TwoLinePreference(Context ctx, AttributeSet attrs) {
super(ctx, attrs);
}
public TwoLinePreference(Context ctx) {
super(ctx);
}
#Override
protected void onBindView(View view) {
super.onBindView(view);
TextView textView = (TextView) view.findViewById(android.R.id.title);
if (textView != null) {
textView.setSingleLine(false);
}
}
}

Use the method setSingleLineTitle(false)
This was added in API 26, so you should be able to use the support library version for older devices

If you want just for a single preference, using setSingleLineTitle or app:singleLineTitle="false" in the XML file.
If you wish to apply it on all preferences, you can do it in multiple ways, when extending PreferenceFragmentCompat :
1.going over all preferences and setting it:
override fun setPreferenceScreen(preferenceScreen: PreferenceScreen?) {
if (preferenceScreen != null)
setAllPreferencesToHaveMultiLineTitles(preferenceScreen)
super.setPreferenceScreen(preferenceScreen)
}
private fun setAllPreferencesToHaveMultiLineTitles(preference: Preference) {
preference.isSingleLineTitle = false
if (preference is PreferenceGroup)
for (i in 0 until preference.preferenceCount)
setAllPreferencesToHaveMultiLineTitles(preference.getPreference(i))
}
2.Doing the same in the adapter, but not recommended as it might not work some day (reaches hidden API of the library) :
override fun onCreateAdapter(preferenceScreen: PreferenceScreen?): RecyclerView.Adapter<*> {
return object : PreferenceGroupAdapter(preferenceScreen) {
#SuppressLint("RestrictedApi")
override fun getItem(position: Int): Preference {
val item = super.getItem(position)
item.isSingleLineTitle = false
return item
}
}
}
3.Similar, but without using isSingleLineTitle (but again not recommended for the same reason) :
override fun onCreateAdapter(preferenceScreen: PreferenceScreen?): RecyclerView.Adapter<*> {
return object : PreferenceGroupAdapter(preferenceScreen) {
#SuppressLint("RestrictedApi")
override fun onCreateViewHolder(parent: ViewGroup, viewType: Int): PreferenceViewHolder {
val holder = super.onCreateViewHolder(parent, viewType)
setPreferenceTitleTextViewToHaveMultipleLines(holder.itemView)
return holder
}
}
}
fun setPreferenceTitleTextViewToHaveMultipleLines(v: View) {
if (v is TextView && v.getId() == android.R.id.title)
return v.setSingleLine(false)
if (v is ViewGroup)
for (i in 0 until v.childCount)
setPreferenceTitleTextViewToHaveMultipleLines(v.getChildAt(i))
}

androidx.preferece library has no longer single line by default in title and summary.

Related

Android custom view set onClickListener with navigation

I've created a custom view that should be a button, but i can't quite get it to work.
So my custom view is extended from View, and I need to to make a fragment navigation when clicked
I've tried to use the override fun performClick() and in it do a rootView.findNavController().navigate(R.id.action_menu_to_settings) but it crashes. I also tried use the .setOnClickLister() { // navigation } but it also doesn't work.
Can anyone tell me how set a clickListener on a custom view for a navigation? Thx
If you are creating a custom view, the best way to handle the click operations is like this:
class MyView #JvmOverloads constructor (
context: Context,
attrs: AttributeSet? = null,
defStyleRes: Int = 0,
defStyleAttr: Int = 0
) : View(context, attrs, defStyleRes, defStyleAttr) {
var touchType = -1 // No user touch yet.
var onClickListener: () -> Unit = {
Log.d(TAG, "on click not yet implemented")
}
override fun onDraw(canvas: Canvas?) {
/* your code here */
}
override fun onTouchEvent(e: MotionEvent?): Boolean {
val value = super.onTouchEvent(e)
when(e?.action) {
MotionEvent.ACTION_DOWN -> {
/* Determine where the user has touched on the screen. */
touchType = 1 // for eg.
return true
}
MotionEvent.ACTION_UP -> {
/* Now that user has lifted his finger. . . */
when (touchType) {
1 -> onClickListener()
}
}
}
return value
}
}
And in your client class (Activity/Fragment), with the instance of the specific custom view that you instantiated, apply the following:
myView.onClickListener = {
findNavController().navigate(R.id.action_menu_to_settings)
}

Recycler View Item Row color change alternatively

I have an issue, I want to have the recycler item row color alternatively. ie, If 1st row is in white color, 2nd row is in grey color and 3rd row is again white and 4th is again grey and so on. I tried one code, but it didn't work. Kindly help.
This is my code,
class ItemAdapter() : RecyclerView.Adapter<ItemAdapter.DateViewHolder>() {
private var ItemList: MutableList<Items>? = ArrayList()
private lateinit var ItemViewModel: ItemRowListBinding
private lateinit var listener: OnItemClickListener
init {
this.ItemList = arrayListOf()
}
override fun onCreateViewHolder(parent: ViewGroup, viewType: Int): DateViewHolder {
ItemViewModel = DataBindingUtil.inflate(
LayoutInflater.from(parent.context), R.layout.item_row_list,
parent, false
)
return DateViewHolder(ItemViewModel)
}
override fun onBindViewHolder(holder: DateViewHolder, position: Int) {
if(position % 2 == 0) {
holder.itemView.setBackgroundResource(R.color.White);
} else {
holder.itemView.setBackgroundResource(R.color.Grey);
}
holder.bindItemDetail(ItemList!![position])
}
override fun getItemCount(): Int {
return ItemList!!.size
}
fun setItemList(scanList: MutableList<Items>) {
this.ItemList = scanList
notifyDataSetChanged()
}
inner class DateViewHolder(private var itemRowBinding: ItemRowListBinding) :
RecyclerView.ViewHolder(itemRowBinding.root) {
fun bindItemDetail(ItemResponse: Items) {
if (itemRowBinding.ItemDetailViewModel == null) {
itemRowBinding.ItemDetailViewModel =
ItemDetailViewModel(
ItemResponse,
itemView.context
)
} else {
itemRowBinding.ItemDetailViewModel!!.setDetail(ItemResponse)
itemRowBinding.executePendingBindings()
}
itemRowBinding.root.Detail.setOnClickListener {
notifyDataSetChanged()
}
itemRowBinding.root.itemLookup.setOnClickListener {
Log.v(
"Clicked_ADAPTER",
"Clicked itemLookup adapter :: position -> $adapterPosition"
)
}
}
}
Any help would be deeply appreciated.
If you read setBackgroundDrawable's documentation, it expects a Drawable resource ID, or 0 to clear the background. A Color is not a Drawable. So you probably want to use this:
holder.itemView.setBackgroundColor(ContextCompat.getColor(context,R.color.white))
If you want to set the drawable you can use setBackgroundResource
Here you are trying to give color in setBackgroundResouce so it is not giving the result
You can set background color using setBackgroundColor
if(position % 2 == 0) {
holder.itemView.setBackgroundColor(ContextCompat.getColor(context,R.color.White));
} else {
holder.itemView.setBackgroundColor(ContextCompat.getColor(context,R.color.Grey));
}
Try this :
It worked for me.
if(position % 2 == 0){
holder.itemView.setBackgroundColor(#A6A2A2);
}else{
holder.itemview.setBackgroundColor(#FFFFFF);
}

Android support EditTextPreference input type

Is there any way to specify the input method type for android.support.v7.preference.EditTextPreference?
If you don't want to use a third party library, it is possible to specify a layout to the EditTextPreference
<EditTextPreference android:defaultValue="0"
android:key="some_key"
android:title="Title"
android:dialogLayout="#layout/preference_edit_text"/>
Then in res/layout/preference_edit_text.xml
<android.support.constraint.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="wrap_content">
<EditText android:id="#android:id/edit"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:inputType="number"
android:singleLine="true"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintTop_toTopOf="parent"
app:layout_constraintBottom_toBottomOf="parent"
android:layout_marginStart="21dp"
android:layout_marginEnd="21dp"/>
</android.support.constraint.ConstraintLayout>
Please note that the edit text id must be : #android:id/edit
but then you are free to use whatever you want inside the android:inputType field
I'm sure there's a better way to align the EditText rather than using 21dp margins
but at least it works
Now one can use Android-Support-Preference-V7-Fix library.Fixed EditTextPreference forwards the XML attributes (like inputType) to the EditText, just like the original preference did.
Here is my version of the answer from Cory Charlton, transfered to Jetpack preferences and written in Kotlin:
import android.content.Context
import android.content.SharedPreferences
import android.text.InputType
import android.util.AttributeSet
import androidx.preference.EditTextPreference
class EditIntegerPreference : EditTextPreference {
constructor(context: Context?) : super(context) {
setInputMethod()
}
constructor(context: Context?, attributeSet: AttributeSet?) : super(context, attributeSet) {
setInputMethod()
}
constructor(context: Context?, attributeSet: AttributeSet?, defStyle: Int) : super(
context,
attributeSet,
defStyle
) {
setInputMethod()
}
override fun getText(): String =
try {
java.lang.String.valueOf(sharedPreferences.getInt(key, 0))
} catch (e: Exception) {
"0"
}
override fun setText(text: String?) {
try {
if (text != null) {
sharedPreferences?.edit()?.putInt(key, text.toInt())?.apply()
summary = text
} else {
sharedPreferences?.remove(key)
summary = ""
}
} catch (e: Exception) {
sharedPreferences?.remove(key)
summary = ""
}
}
override fun onSetInitialValue(defaultValue: Any?) {
val defaultValueInt: Int =
when (defaultValue){
is Int -> defaultValue
is String -> try {defaultValue.toInt()} catch (ex: java.lang.Exception){0}
else -> 0
}
text = sharedPreferences.getInt(key, defaultValueInt).toString()
}
private fun setInputMethod() {
setOnBindEditTextListener {
it.inputType = InputType.TYPE_CLASS_NUMBER
}
}
fun SharedPreferences.remove(key: String) = edit().remove(key).apply()
}
Edit: The previous answers below were built on the stock android.preference.EditTextPreference and unfortunately don't work for the android.support.v7.preference.EditTextPreference.
In the android.preference.EditTextPreference the EditText control is created programmatically and the AttributeSet from the Preference is passed to it.
android.preference.EditTextPreference Source:
public EditTextPreference(Context context, AttributeSet attrs, int defStyleAttr, int defStyleRes) {
super(context, attrs, defStyleAttr, defStyleRes);
mEditText = new EditText(context, attrs);
// Give it an ID so it can be saved/restored
mEditText.setId(com.android.internal.R.id.edit);
/*
* The preference framework and view framework both have an 'enabled'
* attribute. Most likely, the 'enabled' specified in this XML is for
* the preference framework, but it was also given to the view framework.
* We reset the enabled state.
*/
mEditText.setEnabled(true);
}
White allows us to set the inputType on the Preference itself and have it pass through to the EditText. Unfortunately the android.support.v7.preference.EditTextPreference appears to create the EditText in the Layout
See this issue for ideas on working around this:
Just wanted to let you know that subclassing EditTextPreferenceDialogFragment and overriding onAddEditTextToDialogView as well as overriding PreferenceFragmentCompat#onDisplayPreferenceDialog to show that subclass as needed seems to be working fine, thanks for the help.
Create your own class that extends the EditTextPreference and set it there.
Here's my EditIntegerPreference class:
public class EditIntegerPreference extends EditTextPreference {
public EditIntegerPreference(Context context) {
super(context);
}
public EditIntegerPreference(Context context, AttributeSet attributeSet) {
super(context, attributeSet);
}
public EditIntegerPreference(Context context, AttributeSet attributeSet, int defStyle) {
super(context, attributeSet, defStyle);
getEditText().setInputType(InputType.TYPE_CLASS_NUMBER);
getEditText().setSelectAllOnFocus(true);
}
#Override
public String getText() {
try {
return String.valueOf(getSharedPreferences().getInt(getKey(), 0));
} catch (Exception e) {
return getSharedPreferences().getString(getKey(), "0");
}
}
#Override
public void setText(String text) {
try {
if (getSharedPreferences() != null) {
getSharedPreferences().edit().putInt(getKey(), Integer.parseInt(text)).commit();
}
} catch (Exception e) {
// TODO: This catch stinks!
}
}
#Override
protected void onSetInitialValue(boolean restoreValue, Object defaultValue) {
getEditText().setInputType(InputType.TYPE_CLASS_NUMBER);
getEditText().setSelectAllOnFocus(true);
if (restoreValue) {
getEditText().setText(getText());
} else {
super.onSetInitialValue(restoreValue, defaultValue != null ? defaultValue : "");
}
}
}
Note that it is possible to add the inputType attribute to the the EditTextPreference
android:inputType="number"
The reason I didn't go this route is that I wanted my preference to get stored as an Integer and not a String
Workaround for Kotlin + DataStore + androidx.Preference
Disclaimer!
Probably this isn't the way to do it!
Ideally one should only:
set the input to int
override in a DataStore class: putInt/getInt
Extension Function
In my case I had a PreferenceFragmentCompat, so:
fun PreferenceFragmentCompat.setNumericInput(
#StringRes prefRes: Int, initialValue: String) {
val preference = findPreference(getString(prefRes)) as EditTextPreference?
preference?.setOnBindEditTextListener { editText ->
editText.inputType = InputType.TYPE_CLASS_NUMBER or
InputType.TYPE_NUMBER_FLAG_SIGNED
// set the initial value: I read it from the DataStore and then
// pass it as the 2nd argument to setNumericInput.
// BTW, I do store stringPreferenceKeys, as it's the putString method
// that get's triggered
if (editText.text.isEmpty()) editText.setText(initialValue)
editText.setSelection(editText.text.length) // put cursor at the end
}
// to use it in the summary do something like:
preference?.setOnPreferenceChangeListener { it, newValue ->
it.summary = "updated: $newValue"
true
}
}
Also, in my Activity that extends BaseSettingsActivity I
replace the management from SharedPreferences using:
preferenceManager.preferenceDataStore = dataStoreCvLogger

How to make error message in TextInputLayout appear in center

Currently it looks as in attached with this layout:
<android.support.design.widget.TextInputLayout
android:id="#+id/layoutCurrentPW"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_marginTop="10dp"
app:errorEnabled="true">
How to set the error message "password must at least be 8 characters" to center gravity ?
I tried with android:gravity="center" but that did not work.
EDIT
Layout that includes EditText:
<android.support.design.widget.TextInputLayout
android:id="#+id/layoutCurrentPW"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_marginTop="10dp"
app:errorEnabled="true">
<EditText
android:id="#+id/editTextCurrentPassword"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_gravity="bottom"
android:layout_marginLeft="50dp"
android:layout_marginRight="50dp"
android:gravity="center"
android:hint="#string/current_password"
android:inputType="textPassword"
android:textAppearance="?android:attr/textAppearanceMedium"
android:textColor="#color/black" />
</android.support.design.widget.TextInputLayout>
I wanted to know if there is any way to handle it from framework..seems no.
But the way TextInputLayout work is:
- hint will be shown on top of EditText when user touches it.
- Error messages will be shown just under the TextInputLayout and aligned to start.
I had 40dp of left_margin to my EditText due to which misalignment between hint and error message. So for now, I removed left_margin 40dp from EditText and applied same to TextInputLayout itself so it looks fine now.
Lesson learnt :-) is if any margins has to be applied to EditText, better same, if possible, can be applied to TextInputLayout to keep hint and error messages to be placed properly.
class CenterErrorTextInputLayout(context: Context, attrs: AttributeSet) : TextInputLayout(context, attrs) {
override fun setErrorTextAppearance(resId: Int) {
super.setErrorTextAppearance(resId)
val errorTextView = this.findViewById<TextView>(R.id.textinput_error)
val errorFrameLayout = errorTextView.parent as FrameLayout
errorTextView.gravity = Gravity.CENTER
errorFrameLayout.layoutParams = LinearLayout.LayoutParams(LinearLayout.LayoutParams.MATCH_PARENT, LinearLayout.LayoutParams.WRAP_CONTENT)
}}
Here a solution that always centers the errormessage no matter how big your TextInputLayout is.
You make a own class that inherits from TextInputLayout. Then override the ShowError(string text, Drawable icon) method. If the error is called you center the textView with the error.
public class TextInputLayout_Center : TextInputLayout
{
public override void ShowError(string text, Android.Graphics.Drawables.Drawable icon)
{
base.ShowError(text, icon);
centerErrorMessage(this);
}
void centerErrorMessage(ViewGroup view)
{
for (int i = 0; i < view.ChildCount; i++)
{
View v = view.GetChildAt(i);
if (v.GetType() == typeof(TextView))
{
v.LayoutParameters = new LayoutParams(ViewGroup.LayoutParams.MatchParent, v.LayoutParameters.Height);
((TextView)v).Gravity = GravityFlags.CenterHorizontal;
((TextView)v).TextAlignment = TextAlignment.Center;
}
if (v is ViewGroup)
{
centerErrorMessage((ViewGroup)v);
}
}
}
}
I achieved it by setting start margin for the error view. Below you can see my overriden version of setError method of the TextInputLayout component.
#Override
public void setError(#Nullable CharSequence errorText) {
// allow android component to create error view
if (errorText == null) {
return;
}
super.setError(errorText);
// find this error view and calculate start margin to make it look like centered
final TextView errorTextInput = (TextView) findViewById(R.id.textinput_error);
errorTextInput.measure(0, 0);
int errorWidth = errorTextInput.getMeasuredWidth();
int layoutWidth = getWidth();
LinearLayout.LayoutParams params = (LinearLayout.LayoutParams) errorTextInput.getLayoutParams();
params.setMarginStart((layoutWidth - errorWidth) / 2);
errorTextInput.setLayoutParams(params);
}
#Override
public void setErrorEnabled(boolean enabled) {
super.setErrorEnabled(enabled);
if (!enabled)
return;
try {
setErrorGravity(this);
} catch (Exception e) {
e.printStackTrace();
}
}
private void setErrorGravity(ViewGroup view) throws Exception {
for (int i = 0; i < view.getChildCount(); i++) {
View errorView = view.getChildAt(i);
if (errorView instanceof TextView) {
if (errorView.getId() == com.google.android.material.R.id.textinput_error) {
FrameLayout errorViewParent = (FrameLayout) errorView.getParent();
errorViewParent.setLayoutParams(new LinearLayout.LayoutParams(LinearLayout.LayoutParams.MATCH_PARENT, LinearLayout.LayoutParams.WRAP_CONTENT));
((TextView) errorView).setGravity(Gravity.RIGHT);
((TextView) errorView).setTypeface(FontUtils.getTypeFace(view.getContext(), FontUtils.FONT_NAZANIN_TAR));
}
}
if (errorView instanceof ViewGroup) {
setErrorGravity((ViewGroup) errorView);
}
}
}
Hi if you using last version of material design (v 1.2 and above), you have to use this way: (I set gravity to end for support rtl)
Thanks #Emmanuel Guerra
class MyTextInputLayout : TextInputLayout {
constructor(context: Context) : super(context)
constructor(context: Context, attrs: AttributeSet?) : super(context, attrs)
constructor(context: Context, attrs: AttributeSet?, defStyleAttrs: Int) : super(context, attrs, defStyleAttrs)
override fun setErrorEnabled(enabled: Boolean) {
super.setErrorEnabled(enabled)
if (!enabled) {
return
}
try {
changeTextAlignment(com.google.android.material.R.id.textinput_error, View.TEXT_ALIGNMENT_VIEW_END)
val errorView: ViewGroup = this.getChildAt(1) as LinearLayout
val params: LinearLayout.LayoutParams = errorView.layoutParams as LinearLayout.LayoutParams
params.gravity = Gravity.END
errorView.layoutParams = params
//errorView.setPadding(0, 0, 0, 0) //use this to remove error text padding
//setErrorIconDrawable(0)
} catch (e: Exception) {
e.printStackTrace()
}
}
private fun changeTextAlignment(textViewId: Int, alignment: Int) {
val textView = findViewById<TextView>(textViewId)
textView.textAlignment = alignment
}
}
A custom TextInputLayout class for for aligning the error text.
class CenterErrorTextInputLayout #JvmOverloads constructor(
context: Context,
attrs: AttributeSet? = null,
defStyleAttr: Int = 0
) : TextInputLayout(context, attrs, defStyleAttr) {
override fun setErrorEnabled(enabled: Boolean) {
super.setErrorEnabled(enabled)
if (!enabled) return
try {
setErrorTextAlignment()
} catch (e: Exception) {
Timber.e(e, "Failed to set error text : RightErrorTextInputLayout")
}
}
private fun setErrorTextAlignment() {
val errorView: TextView = this.findViewById(R.id.textinput_error)
errorView.textAlignment = View.TEXT_ALIGNMENT_CENTER
}
}
To align the text to the end, use View.TEXT_ALIGNMENT_VIEW_END instead.

Android Progress Bar Listener

Is there anyway to know when a progressbar has reached it maximum. Like a Listener then could plug into the ProgressBar and lets us know that the progress bar is at the maximum level ?
Kind Regards
There isn't a direct way to do this. A workaround could be to make a custom implementation of the ProgressBar and override the setProgress method:
public MyProgressBar extends ProgressBar
{
#Override
public void setProgress(int progress)
{
super.setProgress(progress);
if(progress == this.getMax())
{
//Do stuff when progress is max
}
}
}
I think the cleanest way would be just adding this to your code:
if (progressBar.getProgress() == progressBar.getMax()) {
// do stuff you need
}
If you need onProgressChanged like SeekBar - create custom progress (Kotlin):
class MyProgressBar : ProgressBar {
private var listener: OnMyProgressBarChangeListener? = null
fun setOnMyProgressBarChangeListener(l: OnMyProgressBarChangeListener) {
listener = l
}
constructor(context: Context?) : super(context)
constructor(context: Context?, attrs: AttributeSet?) : super(
context,
attrs
)
constructor(
context: Context?,
attrs: AttributeSet?,
defStyleAttr: Int
) : super(context, attrs, defStyleAttr)
override fun setProgress(progress: Int) {
super.setProgress(progress)
listener?.onProgressChanged(this)
}
interface OnMyProgressBarChangeListener {
fun onProgressChanged(myProgressBar: MyProgressBar?)
}
}
And for example in your fragment:
progress_bar?.setOnMyProgressBarChangeListener(object :
MyProgressBar.OnMyProgressBarChangeListener {
override fun onProgressChanged(myProgressBar: MyProgressBar?) {
val p = progress_bar.progress
// do stuff like this
if (p != 100) {
percentCallback?.changePercent(p.toString()) // show animation custom view with grow percents
} else {
shieldView.setFinishAndDrawCheckMark() // draw check mark
}
}
})
I think overall you should never have to do this. Is there just one valid case where you need to listen to a progressbar progress? I mean, usually it's the other way around: you set the progressbar progress based on something, not the other way around, so you need to track the progress of that something instead of listening to a view (which may or may not exist by the way).

Categories

Resources