题目是这样的,给一大段英文文字,要求实现的效果是点击其中任意一个英文单词会出来英文的释义以及其他信息,这题目有两个难点,一个是,如何将一大段长text分解成每一个独立的并且添加点击事件,第二个难点,是如何自定义Snackbar实现内容的提示,效果如下:
自定义TextView
public class GetWordTextView extends TextView {
private CharSequence mText;
private BufferType mBufferType;
private OnWordClickListener mOnWordClickListener;
private SpannableString mSpannableString;
private BackgroundColorSpan mSelectedBackSpan;
private ForegroundColorSpan mSelectedForeSpan;
private int highlightColor;
private String highlightText;
private int selectedColor;
private int language;//0:english,1:chinese
public GetWordTextView(Context context) {
this(context, null);
}
public GetWordTextView(Context context, AttributeSet attrs) {
this(context, attrs, 0);
}
public GetWordTextView(Context context, AttributeSet attrs, int defStyleAttr) {
super(context, attrs, defStyleAttr);
TypedArray ta = context.obtainStyledAttributes(attrs, me.solidev.getwordtextview.R.styleable.GetWordTextView);
highlightColor = ta.getColor(me.solidev.getwordtextview.R.styleable.GetWordTextView_highlightColor, Color.RED);
highlightText = ta.getString(me.solidev.getwordtextview.R.styleable.GetWordTextView_highlightText);
selectedColor = ta.getColor(me.solidev.getwordtextview.R.styleable.GetWordTextView_selectedColor, Color.BLUE);
language = ta.getInt(me.solidev.getwordtextview.R.styleable.GetWordTextView_language, 0);
ta.recycle();
}
@Override
public void setText(CharSequence text, BufferType type) {
this.mText = text;
mBufferType = type;
setHighlightColor(Color.TRANSPARENT);
setMovementMethod(LinkMovementMethod.getInstance());//没有这句话没有点击效果
setText();
}
private void setText() {
mSpannableString = new SpannableString(mText);//肯定是利用SpannableString设置符合文本 下面是关于SpannableString的一些属性设置
setHighLightSpan(mSpannableString);
//下面处理文字的点击事件
if (language == 0) {
dealEnglish();
} else {
dealChinese();
}
super.setText(mSpannableString, mBufferType);
}
private void dealChinese() {
for (int i = 0; i < mText.length(); i++) {
char ch = mText.charAt(i);
if (Utils.isChinese(ch)) {
mSpannableString.setSpan(getClickableSpan(), i, i + 1, Spanned.SPAN_EXCLUSIVE_EXCLUSIVE);
}
}
}
private void dealEnglish() {
List<WordInfo> wordInfoList = Utils.getEnglishWordIndices(mText.toString());//将输入的英文文本解析出一个包含WordInfo的list
for (WordInfo wordInfo : wordInfoList) {
mSpannableString.setSpan(getClickableSpan(), wordInfo.getStart(), wordInfo.getEnd(), Spanned.SPAN_EXCLUSIVE_EXCLUSIVE);
}
}
private void setHighLightSpan(SpannableString spannableString) {
if (TextUtils.isEmpty(highlightText)) {
return;
}
int hIndex = mText.toString().indexOf(highlightText);
while (hIndex != -1) {
spannableString.setSpan(new ForegroundColorSpan(highlightColor), hIndex, hIndex + highlightText.length(), Spanned.SPAN_EXCLUSIVE_EXCLUSIVE);
hIndex += highlightText.length();
hIndex = mText.toString().indexOf(highlightText, hIndex);
}
}
private void setSelectedSpan(TextView tv) {
if (mSelectedBackSpan == null || mSelectedForeSpan == null) {
mSelectedBackSpan = new BackgroundColorSpan(selectedColor);
mSelectedForeSpan = new ForegroundColorSpan(Color.WHITE);
} else {
mSpannableString.removeSpan(mSelectedBackSpan);
mSpannableString.removeSpan(mSelectedForeSpan);
}
mSpannableString.setSpan(mSelectedBackSpan, tv.getSelectionStart(), tv.getSelectionEnd(), Spanned.SPAN_EXCLUSIVE_EXCLUSIVE);
mSpannableString.setSpan(mSelectedForeSpan, tv.getSelectionStart(), tv.getSelectionEnd(), Spanned.SPAN_EXCLUSIVE_EXCLUSIVE);
GetWordTextView.super.setText(mSpannableString, mBufferType);
}
public void dismissSelected() {
mSpannableString.removeSpan(mSelectedBackSpan);
mSpannableString.removeSpan(mSelectedForeSpan);
GetWordTextView.super.setText(mSpannableString, mBufferType);
}
/**
* 重写点击事件
* @return
*/
private ClickableSpan getClickableSpan() {
return new ClickableSpan() {
@Override
public void onClick(View widget) {
TextView tv = (TextView) widget;
String word = tv.getText().toString().trim().subSequence(tv.getSelectionStart(), tv.getSelectionEnd()).toString();
setSelectedSpan(tv);
if (mOnWordClickListener != null) {
mOnWordClickListener.onClick(word);
}
}
@Override
public void updateDrawState(TextPaint ds) {
}
};
}
public void setOnWordClickListener(OnWordClickListener listener) {
this.mOnWordClickListener = listener;
}
public void setHighLightText(String text) {
highlightText = text;
}
public void setHighLightColor(int color) {
highlightColor = color;
}
public interface OnWordClickListener {
void onClick(String word);
}
}
里面有一个很重要的工具类,将文本分割
class Utils {
private static List<Character> sPunctuations;
static {
Character[] arr = new Character[]{',', '.', ';', '!', '"', ',', '。', '!', ';', '、', ':', '“', '”','?','?'};
sPunctuations = Arrays.asList(arr);
}
static boolean isChinese(char ch) {
return !sPunctuations.contains(ch);
}
@NonNull
static List<WordInfo> getEnglishWordIndices(String content) {
List<Integer> separatorIndices = getSeparatorIndices(content, ' ');//去除开头空格,然后将剩下的英文内容转化为集合
for (Character punctuation : sPunctuations) {
separatorIndices.addAll(getSeparatorIndices(content, punctuation));
}
Collections.sort(separatorIndices);
List<WordInfo> wordInfoList = new ArrayList<>();
int start = 0;
int end;
for (int i = 0; i < separatorIndices.size(); i++) {//判定买一个单词是从哪一个开始哪一个结束
end = separatorIndices.get(i);
if (start == end) {
start++;
} else {
WordInfo wordInfo = new WordInfo();
wordInfo.setStart(start);
wordInfo.setEnd(end);
wordInfoList.add(wordInfo);
start = end + 1;
}
}
return wordInfoList;
}
/**
* 获取每一个字,转化成集合
*
* @param word the content
* @param ch separate char
* @return index array
*/
private static List<Integer> getSeparatorIndices(String word, char ch) {
int pos = word.indexOf(ch);
List<Integer> indices = new ArrayList<>();
while (pos != -1) {
indices.add(pos);
pos = word.indexOf(ch, pos + 1);
}
return indices;
}
}
bean类WordInfo:
public class WordInfo {
private int start;
private int end;
public int getStart() {
return start;
}
public void setStart(int start) {
this.start = start;
}
public int getEnd() {
return end;
}
public void setEnd(int end) {
this.end = end;
}
}
在attrs中的
<declare-styleable name="GetWordTextView">
<attr name="highlightColor" format="reference|color" />
<attr name="highlightText" format="reference|string" />
<attr name="selectedColor" format="reference|color" />
<attr name="language">
<enum name="English" value="0" />
<enum name="Chinese" value="1" />
</attr>
</declare-styleable>
这个自定义TextView写好之后就方便多了,用法如下:
<getword.orchid.com.myapplication.view.GetWordTextView
xmlns:app="http://schemas.android.com/apk/res-auto"
android:id="@+id/english_get_word_text_view"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:textSize="16sp"
app:highlightColor="@color/colorAccent"
app:language="English"
app:selectedColor="@color/colorPrimary"/>
其实和TextView的用法一样,就是多了几个属性,用于点击时和内容的判定.
在MainActivity中这样使用:
mEnglishGetWordTextView.setText(text_info);
mEnglishGetWordTextView.setOnWordClickListener(new GetWordTextView.OnWordClickListener() {
@Override
public void onClick(final String word) {
//点击之后会获取到对应的单词
}
});
关于Snackbar本来也想自定义的,后来发现
public final class Snackbar extends BaseTransientBottomBar<Snackbar>
重写不了,那只能用笨方法自定义了
Snackbar snackbar = Snackbar.make(findViewById(android.R.id.content), "", Snackbar.LENGTH_INDEFINITE);
View snackbarview = snackbar.getView();
Snackbar.SnackbarLayout snackbarLayout = (Snackbar.SnackbarLayout) snackbarview;
View add_view = LayoutInflater.from(snackbarview.getContext()).inflate(R.layout.snackbar_addview, null);//加载布局文件新建View
TextView tv_word = (TextView) add_view.findViewById(R.id.tv_word);
TextView tv_phonogram = (TextView) add_view.findViewById(R.id.tv_phonogram);
TextView tv_info_usa = (TextView) add_view.findViewById(R.id.tv_info_usa);
TextView tv_info_uk = (TextView) add_view.findViewById(R.id.tv_info_uk);
tv_word.setText(word);
tv_phonogram.setText(result.getData().getCn_definition().getDefn());
tv_info_usa.setText("美:[" + result.getData().getPronunciations().getUs() + "]");
tv_info_uk.setText("英:[" + result.getData().getPronunciations().getUk() + "]");
.
.
.
LinearLayout.LayoutParams p = new LinearLayout.LayoutParams(LinearLayout.LayoutParams.WRAP_CONTENT, LinearLayout.LayoutParams.WRAP_CONTENT);//设置新建布局参数
p.gravity = Gravity.CENTER_VERTICAL;//设置新建布局在Snackbar内垂直居中显示
snackbarLayout.addView(add_view, 0, p);
snackbar.show();
在找关于Snackbar 的资料的时候发现了一个很有用的Snackbar 的工具类
public class SnackbarUtil {
public static final int Info = 1;
public static final int Confirm = 2;
public static final int Warning = 3;
public static final int Alert = 4;
public static int red = 0xfff44336;
public static int green = 0xff4caf50;
public static int blue = 0xff2195f3;
public static int orange = 0xffffc107;
/**
* 短显示Snackbar,自定义颜色
* @param view
* @param message
* @param messageColor
* @param backgroundColor
* @return
*/
public static Snackbar ShortSnackbar(View view, String message, int messageColor, int backgroundColor){
Snackbar snackbar = Snackbar.make(view,message, Snackbar.LENGTH_SHORT);
setSnackbarColor(snackbar,messageColor,backgroundColor);
return snackbar;
}
/**
* 长显示Snackbar,自定义颜色
* @param view
* @param message
* @param messageColor
* @param backgroundColor
* @return
*/
public static Snackbar LongSnackbar(View view, String message, int messageColor, int backgroundColor){
Snackbar snackbar = Snackbar.make(view,message, Snackbar.LENGTH_LONG);
setSnackbarColor(snackbar,messageColor,backgroundColor);
return snackbar;
}
/**
* 自定义时常显示Snackbar,自定义颜色
* @param view
* @param message
* @param messageColor
* @param backgroundColor
* @return
*/
public static Snackbar IndefiniteSnackbar(View view, String message, int duration, int messageColor, int backgroundColor){
Snackbar snackbar = Snackbar.make(view,message, Snackbar.LENGTH_INDEFINITE).setDuration(duration);
setSnackbarColor(snackbar,messageColor,backgroundColor);
return snackbar;
}
/**
* 短显示Snackbar,可选预设类型
* @param view
* @param message
* @param type
* @return
*/
public static Snackbar ShortSnackbar(View view, String message, int type){
Snackbar snackbar = Snackbar.make(view,message, Snackbar.LENGTH_SHORT);
switchType(snackbar,type);
return snackbar;
}
/**
* 长显示Snackbar,可选预设类型
* @param view
* @param message
* @param type
* @return
*/
public static Snackbar LongSnackbar(View view, String message, int type){
Snackbar snackbar = Snackbar.make(view,message, Snackbar.LENGTH_LONG);
switchType(snackbar,type);
return snackbar;
}
/**
* 自定义时常显示Snackbar,可选预设类型
* @param view
* @param message
* @param type
* @return
*/
public static Snackbar IndefiniteSnackbar(View view, String message, int duration, int type){
Snackbar snackbar = Snackbar.make(view,message, Snackbar.LENGTH_INDEFINITE).setDuration(duration);
switchType(snackbar,type);
return snackbar;
}
//选择预设类型
private static void switchType(Snackbar snackbar,int type){
switch (type){
case Info:
setSnackbarColor(snackbar,blue);
break;
case Confirm:
setSnackbarColor(snackbar,green);
break;
case Warning:
setSnackbarColor(snackbar,orange);
break;
case Alert:
setSnackbarColor(snackbar, Color.YELLOW,red);
break;
}
}
/**
* 设置Snackbar背景颜色
* @param snackbar
* @param backgroundColor
*/
public static void setSnackbarColor(Snackbar snackbar, int backgroundColor) {
View view = snackbar.getView();
if(view!=null){
view.setBackgroundColor(backgroundColor);
}
}
/**
* 设置Snackbar文字和背景颜色
* @param snackbar
* @param messageColor
* @param backgroundColor
*/
public static void setSnackbarColor(Snackbar snackbar, int messageColor, int backgroundColor) {
View view = snackbar.getView();
if(view!=null){
view.setBackgroundColor(backgroundColor);
((TextView) view.findViewById(R.id.snackbar_text)).setTextColor(messageColor);
}
}
/**
* 向Snackbar中添加view
* @param snackbar
* @param layoutId
* @param index 新加布局在Snackbar中的位置
*/
public static void SnackbarAddView( Snackbar snackbar,int layoutId,int index) {
View snackbarview = snackbar.getView();
Snackbar.SnackbarLayout snackbarLayout=(Snackbar.SnackbarLayout)snackbarview;
View add_view = LayoutInflater.from(snackbarview.getContext()).inflate(layoutId,null);
LinearLayout.LayoutParams p = new LinearLayout.LayoutParams( LinearLayout.LayoutParams.WRAP_CONTENT, LinearLayout.LayoutParams.WRAP_CONTENT);
p.gravity= Gravity.CENTER_VERTICAL;
snackbarLayout.addView(add_view,index,p);
}
}
关于播放单词的读音,发音是来自于网络的url,用了最简单的方法,如下:
MediaPlayer mediaPlayer = new MediaPlayer();
stopMedia(mediaPlayer);
mediaPlayer.setAudioStreamType(AudioManager.STREAM_MUSIC);// 设置媒体流类型
try {
mediaPlayer.setDataSource(result.getData().getUs_audio());
mediaPlayer.prepareAsync();//异步的准备
mediaPlayer.setOnPreparedListener(new MediaPlayer.OnPreparedListener() {
@Override
public void onPrepared(MediaPlayer mp) {
mp.start();
}
});
github地址: https://github.com/orchidTJJ/getword