How is EasyEditSpan used in Android to allow partial text editing? - android

I want to allow the user to edit just part of a line of text in my Android app. I see a class called the EasyEditSpan but when I stick it into a TextView nothing happens. I tried making the TextView editable but it still doesn't have any effect. If switch to an EditText then the whole line of text is editable which is also incorrect. Here is my code:
#Override
public void onCreate(Bundle savedInstanceState) {
TextView testView = (TextView)findViewById(R.id.text_view);
testView.setText(buildMiddleEditSpannable("Please enter your ", "Name", " here."));
}
public static Spannable buildMiddleEditSpannable(CharSequence beginning, CharSequence middle, CharSequence end) {
int spanMidStart = beginning.length();
int spanMidEnd = spanMidStart + middle.length();
SpannableString span = new SpannableString(new StringBuilder(middle).insert(0, beginning).append(end));
span.setSpan(new EasyEditSpan(), spanMidStart, spanMidEnd, 0);
return span;
}

After looking at the framework code referring to EasyEditSpan (EasyEditSpan, TextView and TextUtils), it become apparent that even though it says in its description:
Provides an easy way to edit a portion of text.
The currently available functionality is limited to just the second part of the description as follow:
The TextView uses this span to allow the user to delete a chuck of text in one click. the text. TextView removes this span as soon as the text is edited, or the cursor moves.
Here's some quick sample code that demonstrates its use:
public class EasyEditSpanActivity extends Activity {
#Override
public void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
final EditText editText = new EditText(this);
setContentView(editText);
showToast("Longclick to set EasyEditSpan for the line on cursor");
editText.setInputType(InputType.TYPE_TEXT_FLAG_NO_SUGGESTIONS);
editText.setSingleLine(false);
editText.setText("####\n#######\n###\n######\n####".replace("#", "text "));
editText.setOnLongClickListener(new OnLongClickListener() {
#Override
public boolean onLongClick(View v) {
final Layout layout = editText.getLayout();
final int line = layout.getLineForOffset(editText.getSelectionStart());
final int start = layout.getLineStart(line);
final int end = layout.getLineEnd(line);
editText.getEditableText().setSpan(new EasyEditSpan(), start, end,
Spannable.SPAN_EXCLUSIVE_EXCLUSIVE);
showToast("Edit line to show EasyEdit window");
return true;
}
});
}
private void showToast(String message) {
Toast.makeText(this, message, Toast.LENGTH_SHORT).show();
}
}
So unfortunately if you need a way to allow the user to edit just part of a line of text in your app, EasyEditSpan does not seem to help much. It is likely you will need to implement some code utilizing ClickableSpan and probably a custom dialog.

You might be better of using ClickableSpan and popup a dialog to edit the content. However, I tried to implement partially editable textview. This is not complete and may have some rough edges. It uses three spans and overrides onSelectionChanged(int selStart, int selEnd) and onKeyDown(int keyCode, KeyEvent event) methods to disable edits while the target is part1 or part3. Hope it helps.
//activity_main.xml
<RelativeLayout xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:tools="http://schemas.android.com/tools"
android:layout_width="match_parent"
android:layout_height="match_parent" >
<com.sensei.partialedit.EditTextSelectable
android:id="#+id/text_view"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:padding="#dimen/padding_medium"
android:text="#string/hello_world"
android:background="#00000000"
tools:context=".MainActivity" />
</RelativeLayout>
//MainActivity.java
package com.sensei.partialedit;
import android.app.Activity;
import android.os.Bundle;
public class MainActivity extends Activity {
#Override
public void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
EditTextSelectable testView = (EditTextSelectable) findViewById(R.id.text_view);
testView.setText("Please enter your ", " Name ", " here.");
}
}
//EditTextSelectable.java
package com.sensei.partialedit;
import android.content.Context;
import android.graphics.Color;
import android.text.Spannable;
import android.text.SpannableStringBuilder;
import android.text.method.KeyListener;
import android.text.style.CharacterStyle;
import android.text.style.ForegroundColorSpan;
import android.text.style.UnderlineSpan;
import android.util.AttributeSet;
import android.util.Log;
import android.view.KeyEvent;
import android.widget.EditText;
public class EditTextSelectable extends EditText {
private String part1;
private String part2;
private String part3;
private ForegroundColorSpan span1;
private CharacterStyle span2;
private ForegroundColorSpan span3;
public EditTextSelectable(Context context, AttributeSet attrs, int defStyle) {
super(context, attrs, defStyle);
setTag(getKeyListener());
}
public EditTextSelectable(Context context, AttributeSet attrs) {
super(context, attrs);
setTag(getKeyListener());
}
public EditTextSelectable(Context context) {
super(context);
setTag(getKeyListener());
}
public void setText(String part1, String part2, String part3) {
setText(buildMiddleEditSpannable(part1, part2, part3));
setSelection(part1.length() + part2.length() - 1);
}
private Spannable buildMiddleEditSpannable(String part1, String part2,
String part3) {
this.part1 = part1;
this.part2 = part2;
this.part3 = part3;
SpannableStringBuilder spannable = new SpannableStringBuilder(part1
+ part2 + part3);
span1 = new ForegroundColorSpan(Color.GRAY);
spannable.setSpan(span1, 0, part1.length() - 1,
Spannable.SPAN_EXCLUSIVE_EXCLUSIVE);
span2 = new UnderlineSpan();
spannable.setSpan(span2, part1.length(),
part1.length() + part2.length(),
Spannable.SPAN_INCLUSIVE_INCLUSIVE);
span3 = new ForegroundColorSpan(Color.GRAY);
spannable.setSpan(span3, (part1 + part2).length(),
(part1 + part2 + part3).length(),
Spannable.SPAN_EXCLUSIVE_EXCLUSIVE);
return spannable;
}
#Override
protected void onSelectionChanged(int selStart, int selEnd) {
if (part1 == null)
return;
if (selStart >= getText().getSpanStart(span2)
&& selEnd <= getText().getSpanEnd(span2)) {
setKeyListener((KeyListener) getTag());
setCursorVisible(true);
} else {
setKeyListener(null);
setCursorVisible(false);
}
}
#Override
public boolean onKeyDown(int keyCode, KeyEvent event) {
if (keyCode==KeyEvent.KEYCODE_ENTER) {
// Just ignore the [Enter] key
return true;
}
int selectionStart = getSelectionStart();
int selectionEnd = getSelectionEnd();
Log.d("partialedit", "OnKeyDown:" + selectionStart + ":" + selectionEnd + ":" + getText().getSpanStart(span2) + ":" + getText().getSpanEnd(span2));
if (selectionStart < part1.length() + 1
|| selectionStart >= getText().length() - part3.length()) {
return true;
}
// Handle all other keys in the default way
return super.onKeyDown(keyCode, event);
}
}

Related

How to take bold characters as input in EditText in Android? [duplicate]

This question already has answers here:
Implementing a rich text editor in Android?
(7 answers)
Closed 5 years ago.
I am making a Note Making App which has a feature of adding Bold text via a button. When the bold button is clicked the bold mode should be turned on i.e the text taken from user in EditText should now be Bold. On the next Click on the bold button the bold mode should be turned off i.e text taken from the user should now be of Normal type.
I saw this post. Change future text to bold in EditText in Android
But the answer in that post did not work out.
When I first click the button it takes bold input but when I clicked the button again to take Normal text as input it was still taking bold text as input.
Here is the code:
TextArea.java
package com.example.syed.andtexteditor;
import android.content.Context;
import android.graphics.Typeface;
import android.support.v7.widget.AppCompatEditText;
import android.text.Spannable;
import android.text.style.StyleSpan;
import android.util.AttributeSet;
import android.widget.EditText;
public class TextArea extends AppCompatEditText {
public static final int TYPEFACE_NORMAL = 0;
public static final int TYPEFACE_BOLD = 1;
public static final int TYPEFACE_ITALICS = 2;
public static final int TYPEFACE_BOLD_ITALICS = 3;
private int currentTypeface;
private int lastCursorPosition;
private int tId;
public TextArea(Context context) {
super(context);
lastCursorPosition = this.getSelectionStart();
}
public TextArea(Context context, AttributeSet attrs) {
super(context, attrs);
}
public int gettId() {
return tId;
}
public void settId(int tId) {
this.tId = tId;
}
public void changeTypeface(int tfId) {
currentTypeface = tfId;
lastCursorPosition = this.getSelectionStart();
}
#Override
protected void onTextChanged(CharSequence text, int start, int lengthBefore, int lengthAfter) {
Spannable str = this.getText();
StyleSpan ss;
int endLength = text.toString().length();
switch(currentTypeface) {
case TYPEFACE_NORMAL:
ss = new StyleSpan(Typeface.NORMAL);
break;
case TYPEFACE_BOLD:
ss = new StyleSpan(Typeface.BOLD);
break;
case TYPEFACE_ITALICS:
ss = new StyleSpan(Typeface.ITALIC);
break;
case TYPEFACE_BOLD_ITALICS:
ss = new StyleSpan(Typeface.BOLD_ITALIC);
break;
default:
ss = new StyleSpan(Typeface.NORMAL);
}
str.setSpan(ss, lastCursorPosition, endLength, Spannable.SPAN_EXCLUSIVE_EXCLUSIVE);
}
}
MainActivity.java:
package com.example.syed.andtexteditor;
import android.support.v7.app.AppCompatActivity;
import android.os.Bundle;
import android.view.View;
import android.widget.Button;
import android.widget.CheckBox;
import android.widget.CompoundButton;
public class MainActivity extends AppCompatActivity {
boolean boldClicked=false;
int typefaceStyle = TextArea.TYPEFACE_NORMAL;
#Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
Button boldButton = (Button)findViewById(R.id.bold_button);
boldButton.setOnClickListener(new View.OnClickListener() {
#Override
public void onClick(View view) {
TextArea t = (TextArea) findViewById(R.id.textInput);
if (!boldClicked)
boldClicked = true;
else if (boldClicked)
boldClicked = false;
if (boldClicked) {
typefaceStyle = TextArea.TYPEFACE_BOLD;
t.changeTypeface(typefaceStyle);
}
}
});
}
}
add this line to your layout's Editext
android:textStyle="bold"

How can i get hyperlinks text from textview android?

I am working on text-view.in that text-view display html hyperlink.
when i click on hyper-linked text then i want to get that text.
as per above image if i click on hyperlink, then i want to display text.
Your answer would be appreciated
The LinkEnableTextView class is like this:
import java.util.ArrayList;
import java.util.regex.Matcher;
import java.util.regex.Pattern;
import android.content.Context;
import android.text.Spannable;
import android.text.SpannableString;
import android.text.style.ClickableSpan;
import android.util.AttributeSet;
import android.view.View;
import android.widget.TextView;
public class LinkEnabledTextView extends TextView
{
// The String Containing the Text that we have to gather links from private SpannableString linkableText;
// Populating and gathering all the links that are present in the Text
private ArrayList<Hyperlink> listOfLinks;
// A Listener Class for generally sending the Clicks to the one which requires it
TextLinkClickListener mListener;
// Pattern for gathering #usernames from the Text
Pattern screenNamePattern = Pattern.compile('(#[a-zA-Z0-9_]+)');
// Pattern for gathering #hasttags from the Text
Pattern hashTagsPattern = Pattern.compile('(#[a-zA-Z0-9_-]+)');
// Pattern for gathering http:// links from the Text
Pattern hyperLinksPattern = Pattern.compile('([Hh][tT][tT][pP][sS]?:\\/\\/[^ ,'\'>\\]\\)]*[^\\. ,'\'>\\]\\)])');
public LinkEnabledTextView(Context context, AttributeSet attrs)
{
super(context, attrs);
listOfLinks = new ArrayList<Hyperlink>();
}
public void gatherLinksForText(String text)
{
linkableText = new SpannableString(text);
//gatherLinks basically collects the Links depending upon the Pattern that we supply
//and add the links to the ArrayList of the links
gatherLinks(listOfLinks, linkableText, screenNamePattern);
gatherLinks(listOfLinks, linkableText, hashTagsPattern);
gatherLinks(listOfLinks, linkableText, hyperLinksPattern);
for(int i = 0; i< listOfLinks.size(); i++)
{
Hyperlink linkSpec = listOfLinks.get(i);
android.util.Log.v('listOfLinks :: ' + linkSpec.textSpan, 'listOfLinks :: ' + linkSpec.textSpan);
// this process here makes the Clickable Links from the text
linkableText.setSpan(linkSpec.span, linkSpec.start, linkSpec.end, Spannable.SPAN_EXCLUSIVE_EXCLUSIVE);
}
// sets the text for the TextView with enabled links
setText(linkableText);
}
// sets the Listener for later click propagation purpose
public void setOnTextLinkClickListener(TextLinkClickListener newListener)
{
mListener = newListener;
}
//The Method mainly performs the Regex Comparison for the Pattern and adds them to
//listOfLinks array list
private final void gatherLinks(ArrayList<Hyperlink> links,
Spannable s, Pattern pattern)
{
// Matcher matching the pattern
Matcher m = pattern.matcher(s);
while (m.find())
{
int start = m.start();
int end = m.end();
// Hyperlink is basically used like a structure for storing the information about
// where the link was found.
Hyperlink spec = new Hyperlink();
spec.textSpan = s.subSequence(start, end);
spec.span = new InternalURLSpan(spec.textSpan.toString());
spec.start = start;
spec.end = end;
links.add(spec);
}
}
// This is class which gives us the clicks on the links which we then can use.
public class InternalURLSpan extends ClickableSpan
{
private String clickedSpan;
public InternalURLSpan (String clickedString)
{
clickedSpan = clickedString;
}
#Override
public void onClick(View textView)
{
mListener.onTextLinkClick(textView, clickedSpan);
}
}
// Class for storing the information about the Link Location
class Hyperlink
{
CharSequence textSpan;
InternalURLSpan span;
int start;
int end;
}
Now, having this you require just another interface for propagating the clicks to the place you require to handle them in my case I implemented the interface in my Activity and simple wrote a Log Command there.
The TextLinkClickListener interface is like this:
import android.view.View;
public interface TextLinkClickListener
{
// This method is called when the TextLink is clicked from LinkEnabledTextView
public void onTextLinkClick(View textView, String clickedString)
}
After doing all this you just require to create an Activity using the Custom LinkEnabledTextView and check the things out yourself. There are a few things that you must do while creating a object of the Custom LinkEnabledTextView those are mentioned and described in the Code of the Activity below:
import android.text.method.MovementMethod;
import com.umundoinc.Tvider.Component.LinkEnabledTextView.LinkEnabledTextView;
import com.umundoinc.Tvider.Component.LinkEnabledTextView.TextLinkClickListener;
import android.app.Activity;
import android.graphics.Color;
import android.os.Bundle;
import android.text.method.LinkMovementMethod;
import android.view.View;
//Here the Activity is implementing the TextLinkClickListener the one we have created
//the Clicks over the Links are forwarded over here from the LinkEnabledTextView
public class TextViewActivity extends Activity implements TextLinkClickListener
{
private LinkEnabledTextView check;
protected void onCreate(Bundle savedInstance)
{
super.onCreate(savedInstance);
String text = "This is a #test of regular expressions with http://example.com/ links as used in #twitter for performing various operations based on the links this handles multiple links like http://this_is_fun.com/ and #Awesomess and #Cool";
check = new LinkEnabledTextView(this, null);
check.setOnTextLinkClickListener(this);
check.gatherLinksForText(text);
check.setTextColor(Color.WHITE);
check.setLinkTextColor(Color.GREEN);
MovementMethod m = check.getMovementMethod();
if ((m == null) || !(m instanceof LinkMovementMethod)) {
if (check.getLinksClickable()) {
check.setMovementMethod(LinkMovementMethod.getInstance());
}
}
setContentView(check);
}
public void onTextLinkClick(View textView, String clickedString)
{
android.util.Log.v('Hyperlink clicked is :: ' + clickedString, 'Hyperlink clicked is :: ' + clickedString);
}
Hope it will help you!!
Please try my Implementation:
#Override
public void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
........
textView mTvTxt = findViewById(R.id.tv_txt_view);
final String hyperlink = "Vasant jetava";
SpannableString spannableString = makeLinkSpan(hyperlink , new View.OnClickListener() {
#Override
public void onClick(View v) {
showDialog(hyperlink)
}
});
mTvTxt.setText(getString("hello whats up man....");
mTvTxt.append(spannableString);
makeLinksFocusable(mTvTxt);
}
private SpannableString makeLinkSpan(CharSequence text, View.OnClickListener listener) {
SpannableString link = new SpannableString(text);
link.setSpan(new ClickableString(listener), 0, text.length(),
SpannableString.SPAN_INCLUSIVE_EXCLUSIVE);
return link;
private void makeLinksFocusable(TextView tv) {
MovementMethod m = tv.getMovementMethod();
if ((m == null) || !(m instanceof LinkMovementMethod)) {
if (tv.getLinksClickable()) {
tv.setMovementMethod(LinkMovementMethod.getInstance());
}
}
}
private static class ClickableString extends ClickableSpan {
private View.OnClickListener mListener;
public ClickableString(View.OnClickListener listener) {
mListener = listener;
}
#Override
public void onClick(View v) {
mListener.onClick(v);
}
}
private void showDialog(String message){
AlertDialog alertDialog = new AlertDialog.Builder(<YourActivityName>this).create(); //Read Update
alertDialog.setTitle("Alert Dialog");
alertDialog.setMessage(message);
alertDialog.setButton("Continue..", new DialogInterface.OnClickListener() {
public void onClick(DialogInterface dialog, int which) {
// here you can add functions
}
});
alertDialog.show();
}

Android - Store Spans in SQLiteDatabase

I'm making an app in which I'm using Spannble class to create a highlighter mechanism in an EditText, which works fine. Now, I want to store the span position(s) in a SQLiteDatabase, so that I can reload them and have the Spanned Text to show it in a TextView.
Here's my TextView alongwith the highlighter mechanism -
package com.Swap.RR;
import android.os.Bundle;
import android.app.Activity;
import android.content.Intent;
import android.graphics.Typeface;
import android.text.Editable;
import android.text.Spannable;
import android.text.TextWatcher;
import android.text.style.BackgroundColorSpan;
import android.view.Menu;
import android.view.View;
import android.widget.CompoundButton;
import android.widget.CompoundButton.OnCheckedChangeListener;
import android.widget.EditText;
import android.widget.ToggleButton;
public class Add_New_Note extends Activity {
private EditText note;
private int mStart;
private Typeface Roboto_lt;
private int end;
#Override
public void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_add__new__note);
note = (EditText) findViewById(R.id.notecontent);
ToggleButton highlighter = (ToggleButton) findViewById(R.id.highlightToggle);
Roboto_lt = Typeface.createFromAsset(getAssets(), "Roboto-Light.ttf");
note.setTypeface(Roboto_lt);
mStart = -1;
final TextWatcher highlightWatcher = new TextWatcher() {
public void beforeTextChanged(CharSequence s, int start, int count, int after) {
}
public void onTextChanged(CharSequence s, int start, int before, int count)
{
if(mStart > 0)
{
end = note.getText().length();
note.getText().setSpan(new BackgroundColorSpan(getResources().getColor(R.color.highlighter_green)), mStart, end, Spannable.SPAN_EXCLUSIVE_EXCLUSIVE);
}
}
public void afterTextChanged(Editable s) {
}
};
highlighter.setOnCheckedChangeListener(new OnCheckedChangeListener() {
public void onCheckedChanged(CompoundButton button, boolean isChecked) {
if (isChecked == true) {
//start highlighting when the ToggleButton is ON
if(mStart == -1) mStart = note.getText().length();
else mStart = -1;
mStart = note.getText().length();
note.addTextChangedListener(highlightWatcher);
} else {
//stop highlighting when the ToggleButton is OFF
note.removeTextChangedListener(highlightWatcher);
}
}
});
}
public void noteDone(View v) {
String noteContent = note.getText().toString();
Intent i = new Intent("com.Swap.Notes_page");
i.putExtra("NOTE", noteContent);
setResult(RESULT_OK, i);
finish();
}
#Override
public boolean onCreateOptionsMenu(Menu menu) {
getMenuInflater().inflate(R.menu.activity_add__new__note, menu);
return true;
}
}
Please help me with some suggestions on how to do it. Thanks!
Your best bet is to use Html.toHtml() to convert the Spannable into HTML. You can then use Html.fromHtml() to convert the HTML back into a Spannable when you query the database later on. You will want to do adequate testing, though, as toHtml() and fromHtml() are not guaranteed to support the same HTML tags.

Find a particular regex word and linkify to open an activity in android

I am getting the text from API, can be random, but It contains expressions like on FB
#xyz hi, #abc how u??
1. I need to create a regex expression starts with #, ends with space.
2. Linkify it to open the user activity like XYZ or ABC.
Please help me, I have tried to create a class like,
import android.text.Spannable;
import android.text.SpannableString;
import android.text.Spanned;
import android.text.method.LinkMovementMethod;
import android.text.method.MovementMethod;
import android.text.style.ClickableSpan;
import android.view.View;
import android.widget.TextView;
public class ClickSpan extends ClickableSpan {
private OnClickListener mListener;
public ClickSpan(OnClickListener listener) {
mListener = listener;
}
#Override
public void onClick(View widget) {
if (mListener != null) mListener.onClick();
}
public interface OnClickListener {
void onClick();
}
/**
* To clickify a textview
* #param view
* #param clickableText
* #param listener
*/
public static void clickify(TextView view, final String clickableText,
final ClickSpan.OnClickListener listener) {
CharSequence text = view.getText();
String string = text.toString();
ClickSpan span = new ClickSpan(listener);
int start = string.indexOf(clickableText);
int end = start + clickableText.length();
if (start == -1) return;
if (text instanceof Spannable) {
((Spannable)text).setSpan(span, start, end, Spanned.SPAN_EXCLUSIVE_EXCLUSIVE);
} else {
SpannableString s = SpannableString.valueOf(text);
s.setSpan(span, start, end, Spanned.SPAN_EXCLUSIVE_EXCLUSIVE);
view.setText(s);
}
MovementMethod m = view.getMovementMethod();
if ((m == null) || !(m instanceof LinkMovementMethod)) {
view.setMovementMethod(LinkMovementMethod.getInstance());
}
}
}
And call like,
clickify(textView, clickText,new ClickSpan.OnClickListener()
{
#Override
public void onClick() {
startActivity here..
}
});
I need help as to how I can get this to work on my activity creating a loop of regex.
public class ClickableURLSpan extends URLSpan {
public ClickableURLSpan(String url) {
super(url);
}
#Override
public void onClick(View widget) {
String clickedText = getURL();
// START your activity here
}
}
Now you can linkify your text:
CharSequence input = "#xyz hi, #abc how u??!";
SpannableStringBuilder builder = new SpannableStringBuilder(input);
Pattern pattern = Pattern.compile("#.*?\\s");
Matcher matcher = pattern.matcher(input);
while (matcher.find()) {
int start = matcher.start();
int end = matcher.end();
String text = input.subSequence(start, end).toString();
ClickableURLSpan url = new ClickableURLSpan(text);
builder.setSpan(url, start, end, 0);
}
textView.setText(builder);
textView.setMovementMethod(LinkMovementMethod.getInstance());

Slider on my PreferenceScreen

I want my preference menu to have something to change the duration of a vibration.
There is no slider tag for prefs.xml, so what is the best way to do this?
I improved the link provided by Macarse, so that the value is saved only on ok button click, and so that you can use #string/... values in the XML file.
Here is the code:
/* The following code was written by Matthew Wiggins
* and is released under the APACHE 2.0 license
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Improvements :
* - save the value on positive button click, not on seekbar change
* - handle #string/... values in xml file
*/
package fr.atcm.carpooling.views.utils;
import android.app.AlertDialog;
import android.content.Context;
import android.os.Bundle;
import android.preference.DialogPreference;
import android.util.AttributeSet;
import android.view.Gravity;
import android.view.View;
import android.view.View.OnClickListener;
import android.widget.Button;
import android.widget.LinearLayout;
import android.widget.SeekBar;
import android.widget.TextView;
public class SeekBarPreference extends DialogPreference implements SeekBar.OnSeekBarChangeListener, OnClickListener
{
// ------------------------------------------------------------------------------------------
// Private attributes :
private static final String androidns="http://schemas.android.com/apk/res/android";
private SeekBar mSeekBar;
private TextView mSplashText,mValueText;
private Context mContext;
private String mDialogMessage, mSuffix;
private int mDefault, mMax, mValue = 0;
// ------------------------------------------------------------------------------------------
// ------------------------------------------------------------------------------------------
// Constructor :
public SeekBarPreference(Context context, AttributeSet attrs) {
super(context,attrs);
mContext = context;
// Get string value for dialogMessage :
int mDialogMessageId = attrs.getAttributeResourceValue(androidns, "dialogMessage", 0);
if(mDialogMessageId == 0) mDialogMessage = attrs.getAttributeValue(androidns, "dialogMessage");
else mDialogMessage = mContext.getString(mDialogMessageId);
// Get string value for suffix (text attribute in xml file) :
int mSuffixId = attrs.getAttributeResourceValue(androidns, "text", 0);
if(mSuffixId == 0) mSuffix = attrs.getAttributeValue(androidns, "text");
else mSuffix = mContext.getString(mSuffixId);
// Get default and max seekbar values :
mDefault = attrs.getAttributeIntValue(androidns, "defaultValue", 0);
mMax = attrs.getAttributeIntValue(androidns, "max", 100);
}
// ------------------------------------------------------------------------------------------
// ------------------------------------------------------------------------------------------
// DialogPreference methods :
#Override
protected View onCreateDialogView() {
LinearLayout.LayoutParams params;
LinearLayout layout = new LinearLayout(mContext);
layout.setOrientation(LinearLayout.VERTICAL);
layout.setPadding(6,6,6,6);
mSplashText = new TextView(mContext);
mSplashText.setPadding(30, 10, 30, 10);
if (mDialogMessage != null)
mSplashText.setText(mDialogMessage);
layout.addView(mSplashText);
mValueText = new TextView(mContext);
mValueText.setGravity(Gravity.CENTER_HORIZONTAL);
mValueText.setTextSize(32);
params = new LinearLayout.LayoutParams(
LinearLayout.LayoutParams.FILL_PARENT,
LinearLayout.LayoutParams.WRAP_CONTENT);
layout.addView(mValueText, params);
mSeekBar = new SeekBar(mContext);
mSeekBar.setOnSeekBarChangeListener(this);
layout.addView(mSeekBar, new LinearLayout.LayoutParams(LinearLayout.LayoutParams.FILL_PARENT, LinearLayout.LayoutParams.WRAP_CONTENT));
if (shouldPersist())
mValue = getPersistedInt(mDefault);
mSeekBar.setMax(mMax);
mSeekBar.setProgress(mValue);
return layout;
}
#Override
protected void onBindDialogView(View v) {
super.onBindDialogView(v);
mSeekBar.setMax(mMax);
mSeekBar.setProgress(mValue);
}
#Override
protected void onSetInitialValue(boolean restore, Object defaultValue)
{
super.onSetInitialValue(restore, defaultValue);
if (restore)
mValue = shouldPersist() ? getPersistedInt(mDefault) : 0;
else
mValue = (Integer)defaultValue;
}
// ------------------------------------------------------------------------------------------
// ------------------------------------------------------------------------------------------
// OnSeekBarChangeListener methods :
#Override
public void onProgressChanged(SeekBar seek, int value, boolean fromTouch)
{
String t = String.valueOf(value);
mValueText.setText(mSuffix == null ? t : t.concat(" " + mSuffix));
}
#Override
public void onStartTrackingTouch(SeekBar seek) {}
#Override
public void onStopTrackingTouch(SeekBar seek) {}
public void setMax(int max) { mMax = max; }
public int getMax() { return mMax; }
public void setProgress(int progress) {
mValue = progress;
if (mSeekBar != null)
mSeekBar.setProgress(progress);
}
public int getProgress() { return mValue; }
// ------------------------------------------------------------------------------------------
// ------------------------------------------------------------------------------------------
// Set the positive button listener and onClick action :
#Override
public void showDialog(Bundle state) {
super.showDialog(state);
Button positiveButton = ((AlertDialog) getDialog()).getButton(AlertDialog.BUTTON_POSITIVE);
positiveButton.setOnClickListener(this);
}
#Override
public void onClick(View v) {
if (shouldPersist()) {
mValue = mSeekBar.getProgress();
persistInt(mSeekBar.getProgress());
callChangeListener(Integer.valueOf(mSeekBar.getProgress()));
}
((AlertDialog) getDialog()).dismiss();
}
// ------------------------------------------------------------------------------------------
}
EDIT :
Here is a screenshot :
EDIT : on arlomedia's demand, here are all the needed pieces of code (I just recreated a new projet, it is perfectly working. I corrected some bugs in SeekBarPreference class, so don't forget to re-copy/paste it) :
MainActivity :
package fr.at.testsliderpref;
import android.app.Activity;
import android.content.Intent;
import android.os.Bundle;
import android.view.Menu;
import android.view.MenuItem;
public class MainActivity extends Activity {
#Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
}
#Override
public boolean onCreateOptionsMenu(Menu menu) {
getMenuInflater().inflate(R.menu.main, menu);
return true;
}
#Override
public boolean onMenuItemSelected(int featureId, MenuItem item) {
switch(item.getItemId()) {
case R.id.menu_settings : {
startActivity(new Intent(this, SettingsActivity.class));
break;
}
}
return true;
}
}
SettingsActivity :
package fr.at.testsliderpref;
import android.content.SharedPreferences;
import android.content.SharedPreferences.OnSharedPreferenceChangeListener;
import android.os.Bundle;
import android.preference.PreferenceActivity;
import android.preference.PreferenceFragment;
import android.preference.PreferenceManager;
import fr.at.testsliderpref.utils.SeekBarPreference;
public class SettingsActivity extends PreferenceActivity {
#Override
protected void onCreate(Bundle savedInstanceState) {
// Call super :
super.onCreate(savedInstanceState);
// Set the activity's fragment :
getFragmentManager().beginTransaction().replace(android.R.id.content, new SettingsFragment()).commit();
}
public static class SettingsFragment extends PreferenceFragment implements OnSharedPreferenceChangeListener {
private SeekBarPreference _seekBarPref;
#Override
public void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
// Load the preferences from an XML resource
addPreferencesFromResource(R.xml.activity_settings);
// Get widgets :
_seekBarPref = (SeekBarPreference) this.findPreference("SEEKBAR_VALUE");
// Set listener :
getPreferenceScreen().getSharedPreferences().registerOnSharedPreferenceChangeListener(this);
// Set seekbar summary :
int radius = PreferenceManager.getDefaultSharedPreferences(this.getActivity()).getInt("SEEKBAR_VALUE", 50);
_seekBarPref.setSummary(this.getString(R.string.settings_summary).replace("$1", ""+radius));
}
#Override
public void onSharedPreferenceChanged(SharedPreferences sharedPreferences, String key) {
// Set seekbar summary :
int radius = PreferenceManager.getDefaultSharedPreferences(this.getActivity()).getInt("SEEKBAR_VALUE", 50);
_seekBarPref.setSummary(this.getString(R.string.settings_summary).replace("$1", ""+radius));
}
}
}
layout > activity_main.xml :
<RelativeLayout xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:tools="http://schemas.android.com/tools"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:paddingBottom="#dimen/activity_vertical_margin"
android:paddingLeft="#dimen/activity_horizontal_margin"
android:paddingRight="#dimen/activity_horizontal_margin"
android:paddingTop="#dimen/activity_vertical_margin"
tools:context=".MainActivity" >
<TextView
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="#string/textview_text" />
</RelativeLayout>
menu > main.xml :
<menu xmlns:android="http://schemas.android.com/apk/res/android" >
<item
android:id="#+id/menu_settings"
android:title="#string/menu_settings"
android:icon="#android:drawable/ic_menu_preferences"/>
</menu>
xml > activity_settings.xml :
<?xml version="1.0" encoding="utf-8"?>
<PreferenceScreen xmlns:android="http://schemas.android.com/apk/res/android" >
<fr.at.testsliderpref.utils.SeekBarPreference
android:defaultValue="50"
android:dialogMessage="#string/settings_dialog_message"
android:key="SEEKBAR_VALUE"
android:max="100"
android:summary="#string/settings_summary"
android:text="#string/settings_unit"
android:title="#string/settings_title" />
</PreferenceScreen>
values > strings.xml :
<?xml version="1.0" encoding="utf-8"?>
<resources>
<string name="app_name">TestSliderPref</string>
<string name="textview_text">SeekBarPreference test</string>
<string name="menu_settings">Settings</string>
<string name="settings_dialog_message">Here comes a message</string>
<string name="settings_summary">Current value is $1</string>
<string name="settings_unit">Km</string>
<string name="settings_title">Here comes the title</string>
</resources>
Don't forget to add your SettingsActivity to the manifest, and it should be OK.
You could create your own Preference class that extends DialogPreference and shows a SeekBar as the dialog view.
An easy way to achieve this is to add an empty preference to your preferences.xml which uses a layout including a seekbar.
In your preferences.xml add
<Preference android:layout="#layout/sliderlayout" />
under layouts add the sliderlayout
<?xml version="1.0" encoding="utf-8"?>
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:orientation="vertical" >
<SeekBar
android:id="#+id/seekBar1"
android:layout_width="match_parent"
android:layout_height="wrap_content" />
</LinearLayout>
If you already moved to androidX you can simply use androidx.preference.SeekBarPreference. Documentation here and here
Define it in XML like this:
<SeekBarPreference
app:defaultValue="8"
app:key="COUNT_SPEED"
app:title="Fast count speed"
/>
Note: Now in Android Studio (my current version is 3.2.1) auto-suggestions doesn't work for androidx.preference.PreferenceScreen in prefs.xml, but don't worry inflating settings from xml with setPreferencesFromResource() works fine. All needed attributes names and component references you can find here.
This is good Slider Preference component - Android Slider Preference Library
https://github.com/jayschwa/AndroidSliderPreference
One more implementation of this.
All Credit goes to Tim Autin.
I wanted to have the values displayed loaded from XML Arrays
It looks like this
Code - this now extends ListPreference
import android.app.AlertDialog;
import android.content.Context;
import android.os.Bundle;
import android.preference.ListPreference;
import android.util.AttributeSet;
import android.view.Gravity;
import android.view.View;
import android.widget.Button;
import android.widget.LinearLayout;
import android.widget.SeekBar;
import android.widget.TextView;
public class SeekBarListPreference extends ListPreference implements SeekBar.OnSeekBarChangeListener, View.OnClickListener {
// ------------------------------------------------------------------------------------------
// Private attributes :
private static final String androidns = "http://schemas.android.com/apk/res/android";
private SeekBar mSeekBar;
private TextView mSplashText, mValueText;
private Context mContext;
private String mDialogMessage;
// ------------------------------------------------------------------------------------------
public SeekBarListPreference(Context context, AttributeSet attrs) {
super(context, attrs);
mContext = context;
// Get string value for dialogMessage :
int mDialogMessageId = attrs.getAttributeResourceValue(androidns, "dialogMessage", 0);
if (mDialogMessageId == 0)
mDialogMessage = attrs.getAttributeValue(androidns, "dialogMessage");
else mDialogMessage = mContext.getString(mDialogMessageId);
}
// ------------------------------------------------------------------------------------------
// DialogPreference methods :
#Override
protected View onCreateDialogView() {
LinearLayout.LayoutParams params;
LinearLayout layout = new LinearLayout(mContext);
layout.setOrientation(LinearLayout.VERTICAL);
layout.setPadding(6, 6, 6, 6);
mSplashText = new TextView(mContext);
mSplashText.setPadding(30, 10, 30, 10);
if (mDialogMessage != null)
mSplashText.setText(mDialogMessage);
layout.addView(mSplashText);
mValueText = new TextView(mContext);
mValueText.setGravity(Gravity.CENTER_HORIZONTAL);
mValueText.setTextSize(32);
params = new LinearLayout.LayoutParams(
LinearLayout.LayoutParams.MATCH_PARENT,
LinearLayout.LayoutParams.WRAP_CONTENT);
layout.addView(mValueText, params);
mSeekBar = new SeekBar(mContext);
mSeekBar.setOnSeekBarChangeListener(this);
layout.addView(mSeekBar, new LinearLayout.LayoutParams(LinearLayout.LayoutParams.MATCH_PARENT, LinearLayout.LayoutParams.WRAP_CONTENT));
setProgressBarValue();
return layout;
}
#Override
protected void onPrepareDialogBuilder(AlertDialog.Builder builder) {
// do not call super
}
private void setProgressBarValue() {
String mValue = null;
if (shouldPersist()) {
mValue = getValue();
}
final int max = this.getEntries().length - 1;
mSeekBar.setMax(max);
mSeekBar.setProgress(this.findIndexOfValue(mValue));
}
#Override
protected void onBindDialogView(View v) {
super.onBindDialogView(v);
setProgressBarValue();
}
#Override
public void onProgressChanged(SeekBar seek, int value, boolean fromTouch) {
final CharSequence textToDisplay = getEntryFromValue(value);
mValueText.setText(textToDisplay);
}
private CharSequence getEntryFromValue(int value) {
CharSequence[] entries = getEntries();
return value >= 0 && entries != null ? entries[value] : null;
}
#Override
public void onStartTrackingTouch(SeekBar seekBar) {
}
#Override
public void onStopTrackingTouch(SeekBar seekBar) {
}
#Override
public void showDialog(Bundle state) {
super.showDialog(state);
Button positiveButton = ((AlertDialog) getDialog()).getButton(AlertDialog.BUTTON_POSITIVE);
positiveButton.setOnClickListener(this);
}
#Override
public void onClick(View v) {
if (shouldPersist()) {
final int progressChoice = mSeekBar.getProgress();
setValueIndex(progressChoice);
}
getDialog().dismiss();
}
}
The usage in the preferences file is now
<com.yourfullpackage.SeekBarListPreference
android:defaultValue="0"
android:dialogMessage="#string/time_limit_pref"
android:entries="#array/timeListArray"
android:entryValues="#array/timeListValues"
android:key="time"
android:summary="Select time limit"
android:title="Time"
/>
And the arrays
<string-array name="timeListArray">
<item>10 Seconds</item>
<item>30 Seconds</item>
<item>1 Minute</item>
<item>2 Minutes</item>
<item>Unlimited</item>
</string-array>
<!--This is going to be in seconds-->
<string-array name="timeListValues">
<item>10</item>
<item>30</item>
<item>60</item>
<item>120</item>
<item>0</item>
</string-array>
As a bonus if you already have ListPreferences, you don't need to add anything extra to display the summary as the current value. So this works just fine
#Override
public void onSharedPreferenceChanged(SharedPreferences sharedPreferences, String key) {
SetSummaryForPreferenceKey(key);
}
private void SetSummaryForPreferenceKey(String key) {
Preference preference = findPreference(key);
// This works with our new SeekBarPreference
if (preference instanceof ListPreference) {
ListPreference listPref = (ListPreference) preference;
listPref.setSummary(listPref.getEntry());
}
}
I've made a slight improvement on the code provided in the answer by Tim. This simply makes live adjustments to the output value as the user moves the slider, rather than requiring the user to click the "Ok" button for the changes to be made.
This is useful for things like a Music Volume slider, where the user should be able to hear the adjustment in volume as it's being made.
If the user clicks "OK" the new value is maintained. If the user clicks "Cancel" then the original pre-adjustment value is restored.
Thanks and credit should go to Tim, I just added the extra onClick listener and pushed the value updating into the onChange listener.
package fr.atcm.carpooling.views.utils;
/* The following code was written by Matthew Wiggins
* and is released under the APACHE 2.0 license
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Improvements :
* - save the value on positive button click and/or seekbar change
* - restore pre-adjustment value on negative button click
*/
import android.R;
import android.app.AlertDialog;
import android.content.Context;
import android.os.Bundle;
import android.preference.DialogPreference;
import android.util.AttributeSet;
import android.view.Gravity;
import android.view.View;
import android.view.View.OnClickListener;
import android.widget.Button;
import android.widget.LinearLayout;
import android.widget.SeekBar;
import android.widget.TextView;
public class SeekBarPreference extends DialogPreference implements
SeekBar.OnSeekBarChangeListener, OnClickListener {
// ------------------------------------------------------------------------------------------
// Private attributes :
private static final String androidns = "http://schemas.android.com/apk/res/android";
private SeekBar mSeekBar;
private TextView mSplashText, mValueText;
private Context mContext;
private String mDialogMessage, mSuffix;
private int mDefault, mMax, mValue, mOrig = 0;
// ------------------------------------------------------------------------------------------
// ------------------------------------------------------------------------------------------
// Constructor :
public SeekBarPreference(Context context, AttributeSet attrs) {
super(context, attrs);
mContext = context;
// Get string value for dialogMessage :
int mDialogMessageId = attrs.getAttributeResourceValue(androidns,
"dialogMessage", 0);
if (mDialogMessageId == 0)
mDialogMessage = attrs
.getAttributeValue(androidns, "dialogMessage");
else
mDialogMessage = mContext.getString(mDialogMessageId);
// Get string value for suffix (text attribute in xml file) :
int mSuffixId = attrs.getAttributeResourceValue(androidns, "text", 0);
if (mSuffixId == 0)
mSuffix = attrs.getAttributeValue(androidns, "text");
else
mSuffix = mContext.getString(mSuffixId);
// Get default and max seekbar values :
mDefault = attrs.getAttributeIntValue(androidns, "defaultValue", 0);
mMax = attrs.getAttributeIntValue(androidns, "max", 100);
}
// ------------------------------------------------------------------------------------------
// ------------------------------------------------------------------------------------------
// DialogPreference methods :
#Override
protected View onCreateDialogView() {
LinearLayout.LayoutParams params;
LinearLayout layout = new LinearLayout(mContext);
layout.setOrientation(LinearLayout.VERTICAL);
layout.setPadding(6, 6, 6, 6);
mSplashText = new TextView(mContext);
mSplashText.setPadding(30, 10, 30, 10);
if (mDialogMessage != null)
mSplashText.setText(mDialogMessage);
layout.addView(mSplashText);
mValueText = new TextView(mContext);
mValueText.setGravity(Gravity.CENTER_HORIZONTAL);
mValueText.setTextSize(32);
params = new LinearLayout.LayoutParams(
LinearLayout.LayoutParams.FILL_PARENT,
LinearLayout.LayoutParams.WRAP_CONTENT);
layout.addView(mValueText, params);
mSeekBar = new SeekBar(mContext);
mSeekBar.setOnSeekBarChangeListener(this);
layout.addView(mSeekBar, new LinearLayout.LayoutParams(
LinearLayout.LayoutParams.FILL_PARENT,
LinearLayout.LayoutParams.WRAP_CONTENT));
if (shouldPersist())
mValue = getPersistedInt(mDefault);
mSeekBar.setMax(mMax);
mSeekBar.setProgress(mValue);
return layout;
}
#Override
protected void onBindDialogView(View v) {
super.onBindDialogView(v);
mSeekBar.setMax(mMax);
mSeekBar.setProgress(mValue);
}
#Override
protected void onSetInitialValue(boolean restore, Object defaultValue) {
super.onSetInitialValue(restore, defaultValue);
// Set adjustable value
if (restore)
mValue = shouldPersist() ? getPersistedInt(mDefault) : 0;
else
mValue = (Integer) defaultValue;
// Set original pre-adjustment value
mOrig = mValue;
}
// ------------------------------------------------------------------------------------------
// ------------------------------------------------------------------------------------------
// OnSeekBarChangeListener methods :
#Override
public void onProgressChanged(SeekBar seek, int value, boolean fromTouch) {
String t = String.valueOf(value);
mValueText.setText(mSuffix == null ? t : t.concat(" " + mSuffix));
if (shouldPersist()) {
mValue = mSeekBar.getProgress();
persistInt(mSeekBar.getProgress());
callChangeListener(Integer.valueOf(mSeekBar.getProgress()));
}
}
#Override
public void onStartTrackingTouch(SeekBar seek) {
}
#Override
public void onStopTrackingTouch(SeekBar seek) {
}
public void setMax(int max) {
mMax = max;
}
public int getMax() {
return mMax;
}
public void setProgress(int progress) {
mValue = progress;
if (mSeekBar != null)
mSeekBar.setProgress(progress);
}
public int getProgress() {
return mValue;
}
// ------------------------------------------------------------------------------------------
// ------------------------------------------------------------------------------------------
// Set the positive button listener and onClick action :
#Override
public void showDialog(Bundle state) {
super.showDialog(state);
Button positiveButton = ((AlertDialog) getDialog())
.getButton(AlertDialog.BUTTON_POSITIVE);
Button negativeButton = ((AlertDialog) getDialog())
.getButton(AlertDialog.BUTTON_NEGATIVE);
positiveButton.setOnClickListener(cListenPos);
negativeButton.setOnClickListener(cListenNeg);
}
View.OnClickListener cListenPos = new View.OnClickListener() {
public void onClick(View v) {
if (shouldPersist()) {
mValue = mSeekBar.getProgress();
mOrig = mSeekBar.getProgress();
persistInt(mSeekBar.getProgress());
callChangeListener(Integer.valueOf(mSeekBar.getProgress()));
}
((AlertDialog) getDialog()).dismiss();
}
};
View.OnClickListener cListenNeg = new View.OnClickListener() {
public void onClick(View v) {
if (shouldPersist()) {
mValue = mOrig;
persistInt(mOrig);
callChangeListener(Integer.valueOf(mOrig));
}
((AlertDialog) getDialog()).dismiss();
}
};
#Override
public void onClick(View v) {}
// ------------------------------------------------------------------------------------------
}
One small improvement of Tim's answer and its derivates:
protected void onBindDialogView(View v) {
super.onBindDialogView(v);
mSeekBar.setMax(mMax);
mSeekBar.setProgress(mValue);
String t = String.valueOf(mValue);
mValueText.setText(mSuffix == null ? t : t.concat(" " + mSuffix));
}
It will populate current value at start which is otherwise empty until you actually slide the slider.

Categories

Resources