Multiple Clickable links in TextView on Android

2019-02-06 06:23发布

问题:

I'm trying to add multiple links in a textview similar to what Google & Flipboard has done below with their Terms and conditions AND Privacy Policy shown in the screen shot below:

So far I've stumbled on using this approach

textView.setText(Html.fromHtml(myHtml); textView.setMovementMethod(LinkMovementMethod.getInstance());

where myHtml is a href.

But it doesn't give me control I need e.g to launch a fragment etc.

Any idea how they achieve this in the two examples below?

回答1:

You can use Linkify (android.text.Spannable,java.util.regex.Pattern,java.lang.String)

String termsAndConditions = getResources().getString(R.string.terms_and_conditions);
String privacyPolicy = getResources().getString(R.string.privacy_policy);

legalDescription.setText(
    String.format(
        getResources().getString(R.string.message),
        termsAndConditions,
        privacyPolicy)
);
legalDescription.setMovementMethod(LinkMovementMethod.getInstance());

Pattern termsAndConditionsMatcher = Pattern.compile(termsAndConditions);
Linkify.addLinks(legalDescription, termsAndConditionsMatcher, "terms:");

Pattern privacyPolicyMatcher = Pattern.compile(privacyPolicy);
Linkify.addLinks(legalDescription, privacyPolicyMatcher, "privacy:");

and then you can use the scheme to start an activity for example by adding the scheme in the AndroidManifest:

<intent-filter>
    <category android:name="android.intent.category.DEFAULT" />
    <action android:name="android.intent.action.VIEW" />
    <data android:scheme="terms" />
    <data android:scheme="privacy" />
</intent-filter>

If you want to do a custom action, you can set the intent-filter to your current activity, which will have a singleTop launchmode.

This will cause onNewIntent to be fired where can make your custom actions:

@Override
protected void onNewIntent(final Intent intent) {
 ...
  if (intent.getScheme().equals(..)) {
    ..
  }
}


回答2:

I think that I'm a little late to share this, but I have achieved the same using SpannableStringBuilder.

Simply initialize the TextView that you want to add 2 or more listeners and then pass that to the following method that I have created:

private void customTextView(TextView view) {
        SpannableStringBuilder spanTxt = new SpannableStringBuilder(
                "I agree to the ");
        spanTxt.append("Term of services");
        spanTxt.setSpan(new ClickableSpan() {
            @Override
            public void onClick(View widget) {
                Toast.makeText(getApplicationContext(), "Terms of services Clicked",
                        Toast.LENGTH_SHORT).show();
            }
        }, spanTxt.length() - "Term of services".length(), spanTxt.length(), 0);
        spanTxt.append(" and");
        spanTxt.setSpan(new ForegroundColorSpan(Color.BLACK), 32, spanTxt.length(), 0);
        spanTxt.append(" Privacy Policy");
        spanTxt.setSpan(new ClickableSpan() {
            @Override
            public void onClick(View widget) {
                Toast.makeText(getApplicationContext(), "Privacy Policy Clicked",
                        Toast.LENGTH_SHORT).show();
            }
        }, spanTxt.length() - " Privacy Policy".length(), spanTxt.length(), 0);
        view.setMovementMethod(LinkMovementMethod.getInstance());
        view.setText(spanTxt, BufferType.SPANNABLE);
    } 

And in your XML, use android:textColorLink to add custom link color of your choice. Like this:

   <TextView
     android:id="@+id/textView1"
     android:layout_width="wrap_content"
     android:layout_height="wrap_content"
     android:text="TextView"
     android:textColorLink="#C36241" />  //#C36241 - Rust

And this looks like this:

Hope it helps someone. :)



回答3:

Here's my solution:

First we need to have clickable links in our TextView:

  1. Here's my TextView in the xml layout, do not add any links handling parameters.

    <TextView
            android:id="@+id/sign_up_privacy"
            android:layout_width="wrap_content"
            android:layout_height="wrap_content"
            android:text="@string/terms_and_privacy"/>
    
  2. In strings file, I added the resource text with html tags

    <string name="terms_and_privacy">By signing up you agree to our <a href="terms:">Terms of Service</a> and <a href="privacy:">Privacy Policy.</a></string>
    
  3. In onCreateView set LinkMovementMethod to the TextView

    TextView privacyTextView = (TextView) root.findViewById(R.id.sign_up_privacy);
    privacyTextView.setMovementMethod(LinkMovementMethod.getInstance());
    

Now the TextView links are clickable.

Second, We need to Handle these clicks:

  1. In my Manifest file, I added intent-filter for "terms" and "privacy" and Single Instance Launch Mode

    <activity
                android:name=".MyActivity"
                android:launchMode="singleInstance">
                <intent-filter>
                    <category android:name="android.intent.category.DEFAULT"/>
                    <action android:name="android.intent.action.VIEW"/>
    
                    <data android:scheme="terms"/>
                    <data android:scheme="privacy"/>
                </intent-filter>
            </activity>
    
  2. In MyActivity, override onNewIntent to catch privacy and terms intents

    @Override
        protected void onNewIntent(Intent intent)
        {
            if (intent.getScheme().equalsIgnoreCase(getString("terms")))
            {
                //handle terms clicked
            }
            else if (intent.getScheme().equalsIgnoreCase(getString("privacy")))
            {
                //handle privacy clicked
            }
            else
            {
                super.onNewIntent(intent);
            }
        }
    


回答4:

This is working for me :

in xml :

<TextView
    android:id="@+id/tv_by_continuing_str"
    android:layout_width="match_parent"
    android:layout_height="wrap_content"
    android:layout_marginTop="8dp"
    android:layout_marginLeft="8dp"
    android:layout_marginRight="8dp"
    android:textSize="15sp"
    tools:text="Test msg 1 2, 3"
    android:textColor="@color/translucent_less_white3"
    android:textColorLink="@color/white"
    android:gravity="center|bottom"
    android:layout_above="@+id/btn_privacy_continue" />

In strings.xml

< string name="by_continuing_str2">< ! [ CDATA[By continuing to use this app, you agree to our <a href="https://go.test.com" style="color:gray"/> Privacy Statement </a> and <a href="https://go.test.com" style="color:gray"/>Services Agreement.]]>< / string>

in the activity :

TextView tv_by_continuing = (TextView) findViewById(R.id.tv_by_continuing);
tv_by_continuing.setText(Html.fromHtml(getString(R.string.by_continuing_str2)));
tv_by_continuing.setMovementMethod(LinkMovementMethod.getInstance());


回答5:

An easy fix for this would be to use multiple labels. One for each link and one for the text.

[TermsOfServiceLabel][andLabel][PrivacyPolicy]

Or is it necessary that you only use one label?



回答6:

With Textoo, this can be achieved like:

res/values/strings.xml:

<resources>
     <string name="str_terms_and_privacy">By signing up you agree to our <a href="terms:">Terms of Service</a> and <a href="privacy:">Privacy Policy.</a></string>
</resources>

res/layout/myActivity.xml:

<TextView
    android:id="@+id/view_terms_and_privacy"
    android:text="@string/str_terms_and_privacy"
    />

java/myPackage/MyActivity.java:

public class MyActivity extends Activity {
    ...
    protected void onCreate(Bundle savedInstanceState) {
        ...
        TextView termsAndPrivacy = Textoo
            .config((TextView) findViewById(R.id.view_terms_and_privacy))
            .addLinksHandler(new LinksHandler() {
                @Override
                public boolean onClick(View view, String url) {
                    if ("terms:".equals(url)) {
                        // Handle terms click
                        return true;  // event handled
                    } else if ("privacy:".equals(url)) {
                        // Handle privacy click
                        return true;  // event handled
                    } else {
                        return false;  // event not handled.  continue default processing i.e. launch web browser and display the link
                    }
                }
            })
            .apply();
        ...
    }
    ...
}

This approach have the advantages that:

  • Keep text externalized as string resource. Make it easier to localize your app.
  • Can handle the click event directly using LinksHandler and no need to define additional intent filter
  • Simpler and more readable code