I am using following script to link audio file with a text
TextView link = (TextView) findViewById(R.id.link);
link.setText(buildStringToPlaySound(context, "Play <u>song</u> or <u>poem</u>",new String[] {"http://www.domain.com/song.mp3","http://www.domain.com/poem.mp3"}));
link.setMovementMethod(LinkMovementMethod.getInstance());
public SpannableStringBuilder buildStringToPlaySound(final Context context, String str, final String[] url)
{
CharSequence sequence = Html.fromHtml(str);
SpannableStringBuilder strBuilder = new SpannableStringBuilder(sequence);
UnderlineSpan[] underlines = strBuilder.getSpans(0,strBuilder.length(),UnderlineSpan.class);
//--------------------------------------
int i=0; //line 1
//--------------------------------------
for(UnderlineSpan span : underlines) {
int start = strBuilder.getSpanStart(span);
int end = strBuilder.getSpanEnd(span);
int flags = strBuilder.getSpanFlags(span);
ClickableSpan myActivityLauncher = new ClickableSpan() {
public void onClick(View view) {
try {
//--------------------------------------
playAudio(url[i]); //line 2
//--------------------------------------
} catch (Exception e) {
e.printStackTrace();
}
}
};
strBuilder.setSpan(myActivityLauncher, start, end, flags);
//--------------------------------------
i++; //line 3
//--------------------------------------
}
return strBuilder;
}
Problem is, when I use line 1 as final int i = 0;
Line 3 shows an error
The final local variable i cannot be assigned. It must be blank and not using a compound assignment
But if i use line 1 as int i=0;
Line 2 shows an error
Cannot refer to a non-final variable i inside an inner class defined in a different method
Make variable i as your class instance variable (scope will be through out the class) then it will not alert you to make the variable as final. :)
The variables i is the local variables in the buildStringToPlaySound() method.
When the buildStringToPlaySound() method returns, local variables i will be cleaned up from the stack, so they won't exist anymore after buildStringToPlaySound() returns.
So you can initialize the variable in the anonymous class but in your case make the variable to global so it won't be cleaned from stack.
Getting idea from #Margarita Litkevych's comment
Adding a line final int j=i; inside for loop and replaceing variable i with j inside playAudio(url[j]); working fine.
TextView link = (TextView) findViewById(R.id.link);
link.setText(buildStringToPlaySound(context, "Play <u>song</u> or <u>poem</u>",new String[] {"http://www.domain.com/song.mp3","http://www.domain.com/poem.mp3"}));
link.setMovementMethod(LinkMovementMethod.getInstance());
public SpannableStringBuilder buildStringToPlaySound(final Context context, String str, final String[] url)
{
CharSequence sequence = Html.fromHtml(str);
SpannableStringBuilder strBuilder = new SpannableStringBuilder(sequence);
UnderlineSpan[] underlines = strBuilder.getSpans(0,strBuilder.length(),UnderlineSpan.class);
//--------------------------------------
int i=0; //line 1
//--------------------------------------
for(UnderlineSpan span : underlines) {
//=================================
final int j=i; //added this line
//=================================
int start = strBuilder.getSpanStart(span);
int end = strBuilder.getSpanEnd(span);
int flags = strBuilder.getSpanFlags(span);
ClickableSpan myActivityLauncher = new ClickableSpan() {
public void onClick(View view) {
try {
//------------------------- //==================
playAudio(url[j]); //line 2 //Replaced i with j
//--------------------------//==================
} catch (Exception e) {
e.printStackTrace();
}
}
};
strBuilder.setSpan(myActivityLauncher, start, end, flags);
//--------------------------------------
i++; //line 3
//--------------------------------------
}
return strBuilder;
}
Related
I have a text containing us phone numbers. I would like to make them clickable whatever is the phone language. I investigated how the autolink worked and found the Linkify.addLinks method I tried to use on a custom TextView.
public class PhoneNumberLinkTextView extends android.support.v7.widget.AppCompatTextView {
public PhoneNumberLinkTextView(Context context) {
super(context);
}
public PhoneNumberLinkTextView(Context context, #Nullable AttributeSet attrs) {
super(context, attrs);
}
public PhoneNumberLinkTextView(Context context, #Nullable AttributeSet attrs, int defStyleAttr) {
super(context, attrs, defStyleAttr);
}
public void setUSNumberText(CharSequence text) {
SpannableStringBuilder spanText = new SpannableStringBuilder(text);
if (addLinks(spanText)) {
setText(spanText);
} else {
setText(text);
}
}
public boolean addLinks(#NonNull SpannableStringBuilder text) {
ArrayList<LinkSpec> links = new ArrayList<>();
gatherTelLinks(links, text);
if (links.isEmpty()) {
return false;
}
Object[] spans = text.getSpans(0, text.length(), Object.class);
final int count = spans.length;
for (int i = 0; i < count; i++) {
text.removeSpan(spans[i]);
}
for (LinkSpec link: links) {
applyLink(link.url, link.start, link.end, text);
}
return true;
}
private void gatherTelLinks(ArrayList<LinkSpec> links, Spannable s) {
PhoneNumberUtil phoneUtil = PhoneNumberUtil.getInstance();
Iterable<PhoneNumberMatch> matches = phoneUtil.findNumbers(s.toString(),
Locale.US.getCountry(), PhoneNumberUtil.Leniency.POSSIBLE, Long.MAX_VALUE);
for (PhoneNumberMatch match : matches) {
LinkSpec spec = new LinkSpec();
spec.url = "tel:" + normalizeNumber(match.rawString());
spec.start = match.start();
spec.end = match.end();
links.add(spec);
}
}
private void applyLink(String url, int start, int end, Spannable text) {
URLSpan span = new URLSpan (url);
text.setSpan(span, start, end, Spanned.SPAN_EXCLUSIVE_EXCLUSIVE);
}
/**
* Normalize a phone number by removing the characters other than digits. If
* the given number has keypad letters, the letters will be converted to
* digits first.
*
* #param phoneNumber the number to be normalized.
* #return the normalized number.
*/
public String normalizeNumber(String phoneNumber) {
if (TextUtils.isEmpty(phoneNumber)) {
return "";
}
StringBuilder sb = new StringBuilder();
int len = phoneNumber.length();
for (int i = 0; i < len; i++) {
char c = phoneNumber.charAt(i);
// Character.digit() supports ASCII and Unicode digits (fullwidth, Arabic-Indic, etc.)
int digit = Character.digit(c, 10);
if (digit != -1) {
sb.append(digit);
} else if (sb.length() == 0 && c == '+') {
sb.append(c);
} else if ((c >= 'a' && c <= 'z') || (c >= 'A' && c <= 'Z')) {
return normalizeNumber(PhoneNumberUtils.convertKeypadLettersToDigits(phoneNumber));
}
}
return sb.toString();
}
class LinkSpec {
String url;
int start;
int end;
}
}
This code is currently visually working. My US number is formatted as I expect it to be but my phone number is not clickable.
I then tried to add a setMovementMethod(LinkMovementMethod.getInstance()) after my setText() but this time I lost my US number formatted as a phone number.
Does anyone know how I can achieve what I'm trying to do ?
I ended solving my problem by replacing URLSpan by a custom class extending ClickableSpan.
private class USNumberSpan extends ClickableSpan {
private String url;
USNumberSpan(String url) {
this.url = url;
}
#Override
public void onClick(View widget) {
Uri uri = Uri.parse(url);
Context context = widget.getContext();
Intent intent = new Intent(Intent.ACTION_VIEW, uri);
intent.putExtra(Browser.EXTRA_APPLICATION_ID, context.getPackageName());
try {
context.startActivity(intent);
} catch (ActivityNotFoundException e) {
Log.w("URLSpan", "Activity was not found for intent, " + intent.toString(), e);
}
}
}
I did not find out why this is working whereas URLSpan is not but I'm glad it worked.
I've found a Kotlin answer (by the user Iliya Mashin) with a Pattern for any type of numbers on this link: android:autoLink for phone numbers doesn't always work
I adapted it for Java and specified at least 4 numbers in the end (so it won't linkify some zipcodes ending with 3 numbers "xxxxx-xxx"), so, if you don't want this specific limitation, just remove the "{4,}" in the end of the expression).
LinkifyCompat.addLinks(textView, Linkify.ALL); // This will use the usual linkify for any other format
Pattern pattern = Pattern.compile("([\\d|\\(][\\h|\\(\\d{3}\\)|\\.|\\-|\\d]{4,}\\d{4,})", Pattern.CASE_INSENSITIVE);
LinkifyCompat.addLinks(textView, pattern, "tel://", null, null, null); // this adds the format for all kinds of phone number
If you want to link just the numbers, remove the first line (the one with "Linkify.ALL").
I am trying to generate random number. And to do so ,
I am getting the text from edit text and passing it to the nextInt of Random class. But i have a problem. My textView is not changing.
Here its the code:
package random.vivek.com.random;
import android.os.Bundle;
import android.support.v7.app.AppCompatActivity;
import android.widget.EditText;
import android.widget.TextView;
/**
* Created by Shiva on 09-08-2015.
*/
public class Random extends AppCompatActivity {
int number , i ;
#Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
java.util.Random genertor = new java.util.Random();
EditText editText = (EditText) findViewById(R.id.random);
TextView textView = (TextView) findViewById(R.id.text);
String s = editText.getText().toString();
try {
i = Integer.parseInt(s);
for (int roll = 0; roll < 20; roll++) {
number = genertor.nextInt(i);
}
} catch (NumberFormatException e) {
e.printStackTrace();
}
textView.setText(number + "");
}
}
Try:
try {
i = Integer.parseInt(s);
for (int roll = 0; roll < 20; roll++) {
number = genertor.nextInt(i);
textView.setText(number);
}
} catch (NumberFormatException e) {
e.printStackTrace();
You put all your code in the onCreate method, which is wrong. The onCreate method is called when the activity starts. You should add a button or something to your app so that when the user presses the button, a random number gets generated. I think you have the ability to do that so I won't show you here.
In the on click listener of the button, write your code above:
public void buttonOnClick (View view) {
java.util.Random genertor = new java.util.Random();
EditText editText = (EditText) findViewById(R.id.random);
TextView textView = (TextView) findViewById(R.id.text);
String s = editText.getText().toString();
try {
i = Integer.parseInt(s);
for (int roll = 0; roll < 20; roll++) {
number = genertor.nextInt(i);
}
textView.setText (Integer.toString(number));
} catch (NumberFormatException e) {
e.printStackTrace();
}
String s = editText.getText().toString();
try {
i = Integer.parseInt(s);
for (int roll = 0; roll < 20; roll++) {
number = genertor.nextInt(i);
textView.setText(String.ValueOf()number);
// But this will also keep continuing through the loop and change the text quickly.
}
If you want to append the values:
String s = editText.getText().toString();
try {
i = Integer.parseInt(s);
for (int roll = 0; roll < 20; roll++) {
number = genertor.nextInt(i);
textView.append(String.ValueOf()number + " ");
Also as it stands, your textView.setText(number + ""); the +"" does nothing.
You need to change your algorithm if you want a time delay between changing the Text of your textView.
You need to set your textview by using textView.setText(Integer.toString(number)) in your code
onCreate method:
First things first, you are writing this in the onCreate method. onCreate is the method where most of your initialization stuff goes.
So when the view is created there is nothing in the edittext, so you get nothing in the text view. Instead you should be doing it on the click of some button or on text change in edit text to make it more dynamic.
Responding to edit text change:
To perform some action on edittext text change use the text watcher as follows:
editText.addTextChangedListener(new TextWatcher(){
public void afterTextChanged(Editable s) {
String s = editText.getText().toString();
try {
i = Integer.parseInt(s);
for (int roll = 0; roll < 20; roll++) {
number = genertor.nextInt(i);
}
} catch (NumberFormatException e) {
e.printStackTrace();
}
}
public void beforeTextChanged(CharSequence s, int start, int count, int after){}
public void onTextChanged(CharSequence s, int start, int before, int count){}
});
i am making a chatting application in which i am using emoticons functionality.My Emoticons functionality is working properly for single image ,but when i am taking multiple emotive images it is not converting in to particular image..,at a time only single image is converting, My problem is
i am unable to separate the spanned object in edit text field..,for single value it is working but for multiple value its is not working..
Example.i am taking 4 different images in edit text field, like this here
now i want to seprate its spanned object.,how can i do this
here is code
public void keyClickedIndex( final String index)
{
ImageGetter imageGetter = new ImageGetter()
{
public Drawable getDrawable(String source)
{
StringTokenizer st = new StringTokenizer(index, ".");
Drawable d = new BitmapDrawable(getResources(),emoticons[Integer.parseInt(st.nextToken()) - 1]);
d.setBounds(0, 0, d.getIntrinsicWidth(), d.getIntrinsicHeight());
return d;
}
};
Spanned cs = Html.fromHtml("<img src ='"+ index +"'/>", imageGetter, null);
int cursorPosition = mSendText.getSelectionStart();
mSendText.getText().insert(cursorPosition, cs);
please help me..,Thanks in Advance.
you can use emoticon handler method
private static class EmoticonHandler implements TextWatcher {
private final EditText mEditor;
private final ArrayList<ImageSpan> mEmoticonsToRemove = new ArrayList<ImageSpan>();
//public String txt;
XMPPClient act;
public EmoticonHandler(EditText editor,XMPPClient act) {
// Attach the handler to listen for text changes.
mEditor = editor;
mEditor.addTextChangedListener(this);
this.act = act;
}
public void insert(String emoticon, int resource)
{
// Create the ImageSpan
Drawable drawable = mEditor.getResources().getDrawable(resource);
drawable.setBounds(0, 0, drawable.getIntrinsicWidth(), drawable.getIntrinsicHeight());
ImageSpan span = new ImageSpan(drawable,emoticon,ImageSpan.ALIGN_BASELINE);
// Get the selected text.
int start = mEditor.getSelectionStart();
int end = mEditor.getSelectionEnd();
Editable message = mEditor.getEditableText();
// Insert the emoticon.
message.replace(start, end, emoticon);
message.setSpan(span, start, start + emoticon.length(), Spanned.SPAN_EXCLUSIVE_EXCLUSIVE);
}
#Override
public void beforeTextChanged(CharSequence text, int start, int count, int after) {
// Check if some text will be removed.
if (count > 0) {
int end = start + count;
Editable message = mEditor.getEditableText();
ImageSpan[] list = message.getSpans(start, end, ImageSpan.class);
boolean check = false;
for (ImageSpan span : list)
{
// Get only the emoticons that are inside of the changed
// region.
check = true;
int spanStart = message.getSpanStart(span);
int spanEnd = message.getSpanEnd(span);
//txt = text.toString();
act.emorTxt = text.toString();
if ((spanStart < end) && (spanEnd > start)) {
// Add to remove list
mEmoticonsToRemove.add(span);
}
}
if(!check)
{
act.emorTxt = text.toString();
}
}
}
#Override
public void afterTextChanged(Editable text) {
Editable message = mEditor.getEditableText();
// Commit the emoticons to be removed.
for (ImageSpan span : mEmoticonsToRemove)
{
int start = message.getSpanStart(span);
int end = message.getSpanEnd(span);
// Remove the span
message.removeSpan(span);
// Remove the remaining emoticon text.
if (start != end) {
message.delete(start, end);
}
}
mEmoticonsToRemove.clear();
}
#Override
public void onTextChanged(CharSequence text, int start, int before, int count) {
}
}
it will work perfectly....:)
Because my log command kicks out a "4" I am guessing it is:
word.getnewword();
that is returning the null pointer exception, but why?
The aim of the code is to have a class that will hold different letters in a word for me to then animate each letter in the word separately then change the word to a random new one from a list in the class. I am using char for each letter instead of a string for the word or each letter, is that ok also?
package com.xyz.letters;
import ......abreviated
public class letters extends Activity {
private Animals word;
boolean gameover = false;
private AdView adView;
TextView tva;
TextView tvb;
TextView tvc;
TextView tvd;
TextView tve;
TextView tvf;
#Override
public void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.letters);
Log.e("main", "xml loaded fine");
tva = (TextView) findViewById(R.id.texta);
tvb = (TextView) findViewById(R.id.textb);
tvc = (TextView) findViewById(R.id.textc);
tvd = (TextView) findViewById(R.id.textd);
tve = (TextView) findViewById(R.id.texte);
tvf = (TextView) findViewById(R.id.textf);
Log.e("main", "textview pre-setup complete");
Log.e("main", "4");
word.getnewword();
Log.e("main", "gotword " + word.getwordchosennumber());
setletters();
Log.e("main", "set letters ok");
......etc
My class:-
package com.xyz.letters;
import java.util.Random;
import android.util.Log;
public class Animals {
private int wordchosen;
private int length;
private char l1;
private char l2;
private char l3;
private char l4;
private char l5;
private char l6;
public Animals() {
this.wordchosen = 0;
this.length = 3;
this.l1 = 'a';
this.l2 = 'b';
this.l3 = 'c';
this.l4 = 'a';
this.l5 = 'b';
this.l6 = 'c';
}
public void getnewword() {
Log.e("animals", "0");
Random ran = new Random();
Log.e("animals", "1");
wordchosen = ran.nextInt(2);
Log.e("animals", "2");
switch (wordchosen) {
case 0:
length = 3;
l1 = 'a';
l2 = 'n';
l3 = 't';
break;
case 1:
length = 4;
l1 = 'b';
l2 = 'a';
l3 = 'l';
l4 = 'l';
break;
case 2:
length = 3;
l1 = 'b';
l2 = 'a';
l3 = 't';
break;
default:
length = 0;
break;
}
}
public int getwordchosennumber()
{
return this.wordchosen;
}
public int getlength()
{
return this.length;
}
public int getl1()
{
return this.l1;
}
public int getl2()
{
return this.l2;
}
public int getl3()
{
return this.l3;
}
public int getl4()
{
return this.l4;
}
public int getl5()
{
return this.l5;
}
public int getl6()
{
return this.l6;
}
}
Because, word has null value. The exception null pointer exception itself specify its meaning that, you want to access some object, which has null reference.
Declared here
private Animals word;
Initially, it has null reference, until you won't do something like
word = new Animals(); // assign Animals reference
You never initialize word. On the line before the error (or in the initial declaration), use new:
word = new Animals(); // The line to insert.
word.getnewword();
On future questions, when you get an exception or force close, please post the full LogCat stack trace (red text in your log output), to help us find the problem quicker.
In the OnCreate after
Log.e("main", "textview pre-setup complete");
add
word = new Animals();
You should create the object of the class like
word = new Animals();
now, use this object to access the public methods and variable of Animals class.
word.getnewword();
You're getting a NullPointerException because the variable "word" wasn't initialized. Android is a little bit strange when it comes to working with variables. If you don't initialize the variable in the onCreate method (or some method that is called from the onCreate method) before you call the variable then you will get a NullPointerException because the JVM doesn't know what the value of that variable is. It's kind of like regular java code that has a main method - if you call a variable that hasn't been inialized in the main method you'll get a NullPointerException.
To fix your code simply put this in the onCreateMethod before you make any calls to the variable:
word = new Animals();
After that you can do what you need with the variable.
I have a function to build clickable tags for a textview. It goes as follows:
private CharSequence tagsBuilder(String text, String token) {
SpannableStringBuilder builtTags = new SpannableStringBuilder();
int start = 0, end = 0;
for(int i = 0; i < 5; i++) {
start = 0;
end = text.indexOf(token, 0);
try {
if(start < end) {
SpannableStringBuilder ssb = new SpannableStringBuilder(text.substring(start, end));
ssb.setSpan(new ClickableSpan() {
#Override
public void onClick(View v)
{
Log.i("DEBUGTAG", "Span clicked - " + ((TextView) v).getText());
}
}, start, end, 0);
builtTags.append(ssb);
builtTags.append(" ");
text = text.substring(end + 1);
}
} catch (IndexOutOfBoundsException e) {
break;
}
}
return builtTags;
}
I can see the textview with 5 individually clickable tags. But the problem is, the Log that prints for any tag that is clicked is whole text of the textview.
Am I doing something wrong here? How do I get the text of individual tags that were clicked.
Your log line is the following:
Log.i("DEBUGTAG", "Span clicked - " + ((TextView) v).getText());
That logs the contents of the TextView. So... you get the text of the TextView. If you want to get token in there, you'll have to copy that in.
Here's something you can try:
private CharSequence tagsBuilder(String text, final String token) {
SpannableStringBuilder builtTags = new SpannableStringBuilder();
int start = 0, end = 0;
for(int i = 0; i < 5; i++) {
start = 0;
end = text.indexOf(token, 0);
try {
if(start < end) {
SpannableStringBuilder ssb = new SpannableStringBuilder(text.substring(start, end));
ssb.setSpan(new ClickableSpan() {
private String mText = token;
#Override
public void onClick(View v)
{
Log.i("DEBUGTAG", "Span clicked - " + mText);
}
}, start, end, 0);
builtTags.append(ssb);
builtTags.append(" ");
text = text.substring(end + 1);
}
} catch (IndexOutOfBoundsException e) {
break;
}
}
return builtTags;
}