├── .gitignore
├── .idea
├── caches
│ └── build_file_checksums.ser
├── codeStyles
│ └── Project.xml
├── gradle.xml
├── inspectionProfiles
│ └── Project_Default.xml
├── misc.xml
└── runConfigurations.xml
├── README.md
├── app
├── .gitignore
├── build.gradle
├── proguard-rules.pro
├── screenshoots
│ ├── 2.gif
│ ├── 3.png
│ ├── collapse.gif
│ ├── 基础.gif
│ ├── 折叠Text.gif
│ ├── 效果1.gif
│ ├── 效果2.gif
│ └── 设置paddin_bottom.png
└── src
│ └── main
│ ├── AndroidManifest.xml
│ ├── java
│ └── com
│ │ └── example
│ │ └── apple
│ │ ├── keyboarddemo
│ │ ├── RedDotView.java
│ │ ├── Register2Activity.java
│ │ ├── RegisterActivity.java
│ │ ├── SessionActivity.java
│ │ ├── SplashActivity.java
│ │ ├── collapse
│ │ │ ├── CollapseActivity.java
│ │ │ └── ExpandLayout.java
│ │ ├── expand
│ │ │ └── ExpandTextView.java
│ │ ├── focus
│ │ │ └── SoftFocusActivity.java
│ │ ├── keyboard_num
│ │ │ ├── CustomNumKeyboardView.java
│ │ │ ├── KeyBoardNumberActivity.java
│ │ │ └── KeyboardNumUtil.java
│ │ ├── shelter_aty
│ │ │ └── ShelterActivity.java
│ │ ├── top_dismiss
│ │ │ ├── TopDismissFirstActivity.java
│ │ │ ├── TopDismissSecondActivity.java
│ │ │ └── TopDismissThirdActivity.java
│ │ └── topic
│ │ │ ├── BottomDialog.java
│ │ │ ├── BottomSheetBar.java
│ │ │ ├── RichEditText.java
│ │ │ └── TopicActivity.java
│ │ └── todo.txt
│ └── res
│ ├── drawable-v24
│ └── ic_launcher_foreground.xml
│ ├── drawable
│ ├── bg_border_color_black.xml
│ ├── bg_comment.xml
│ ├── bg_splash.jpg
│ ├── ic_bg_edit.xml
│ ├── ic_bg_edit_active.xml
│ ├── ic_bg_edit_normal.xml
│ ├── ic_launcher_background.xml
│ ├── ic_selector_checkbox.xml
│ ├── iconmbile.png
│ ├── keyboard_btn_bg.xml
│ ├── keyboard_btn_normal.9.png
│ ├── keyboard_btn_normal_bg.xml
│ ├── keyboard_btn_pressed.9.png
│ ├── keyboard_cofirm_btn_bg.xml
│ ├── keyboard_confirm_btn.9.png
│ ├── keyboard_confirm_btn_pressed.9.png
│ ├── keyboard_delete_btn.9.png
│ ├── keyboard_delete_btn_bg.xml
│ ├── keyboard_delete_btn_pressed.9.png
│ ├── keyboard_delete_icon.png
│ ├── keyboard_fold_icon.png
│ ├── selector_edit_focus.xml
│ ├── shape_bottom_gray.xml
│ ├── shape_bottom_green.xml
│ ├── shape_session_btn_send.xml
│ └── shape_session_btn_voice_normal.xml
│ ├── layout
│ ├── activity_collapse.xml
│ ├── activity_keyboard_number.xml
│ ├── activity_register.xml
│ ├── activity_register2.xml
│ ├── activity_session.xml
│ ├── activity_shelter.xml
│ ├── activity_soft_focus.xml
│ ├── activity_splash.xml
│ ├── activity_top_dismiss_first.xml
│ ├── activity_top_dismiss_second.xml
│ ├── activity_top_dismiss_third.xml
│ ├── activity_topic.xml
│ ├── layout_bottom_sheet_comment_bar.xml
│ ├── layout_test.xml
│ └── textview_expand_shink.xml
│ ├── mipmap-anydpi-v26
│ ├── ic_launcher.xml
│ └── ic_launcher_round.xml
│ ├── mipmap-hdpi
│ ├── ic_cheat_add.png
│ ├── ic_cheat_emo.png
│ ├── ic_cheat_voice.png
│ ├── ic_launcher.png
│ └── ic_launcher_round.png
│ ├── mipmap-mdpi
│ ├── ic_back2.png
│ ├── ic_launcher.png
│ └── ic_launcher_round.png
│ ├── mipmap-xhdpi
│ ├── ic_expand_down.png
│ ├── ic_expand_up.png
│ ├── ic_launcher.png
│ ├── ic_launcher_round.png
│ └── keyicon.png
│ ├── mipmap-xxhdpi
│ ├── ic_launcher.png
│ └── ic_launcher_round.png
│ ├── mipmap-xxxhdpi
│ ├── form_checkbox_checked.png
│ ├── form_checkbox_normal.png
│ ├── ic_launcher.png
│ └── ic_launcher_round.png
│ ├── values-v19
│ └── styles.xml
│ ├── values
│ ├── colors.xml
│ ├── dimens.xml
│ ├── layout_style.xml
│ ├── strings.xml
│ └── styles.xml
│ └── xml
│ └── keyboard_num_woith_dot.xml
├── build.gradle
├── gradle.properties
├── gradle
└── wrapper
│ ├── gradle-wrapper.jar
│ └── gradle-wrapper.properties
├── gradlew
├── gradlew.bat
└── settings.gradle
/.gitignore:
--------------------------------------------------------------------------------
1 | *.iml
2 | .gradle
3 | /local.properties
4 | /.idea/libraries
5 | /.idea/modules.xml
6 | /.idea/workspace.xml
7 | .DS_Store
8 | /build
9 | /captures
10 | .externalNativeBuild
11 |
--------------------------------------------------------------------------------
/.idea/caches/build_file_checksums.ser:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/crazyzhangxl/KeyBoardDemo/d7c1bd3eb087e3bb542e29b0686c89f9f28b8843/.idea/caches/build_file_checksums.ser
--------------------------------------------------------------------------------
/.idea/codeStyles/Project.xml:
--------------------------------------------------------------------------------
1 |
9 |
20 |
25 |
35 |
15 | * 带过渡动画的折叠收缩布局
16 | */
17 | public class ExpandLayout extends LinearLayout {
18 |
19 | public ExpandLayout(Context context) {
20 | this(context, null);
21 | }
22 |
23 | public ExpandLayout(Context context, AttributeSet attrs) {
24 | this(context, attrs, 0);
25 | }
26 |
27 | public ExpandLayout(Context context, AttributeSet attrs, int defStyle) {
28 | super(context, attrs, defStyle);
29 | initView();
30 | }
31 |
32 | private View layoutView;
33 | private int viewHeight;
34 | private boolean isExpand;
35 | private long animationDuration;
36 | private boolean lock;
37 | private ImageView mImageView;
38 |
39 | private void initView() {
40 | setOrientation(VERTICAL);
41 | layoutView = this;
42 | isExpand = true;
43 | animationDuration = 300;
44 | setViewDimensions();
45 | }
46 |
47 | /**
48 | * @param isExpand 初始状态是否折叠
49 | */
50 | public void initExpand(boolean isExpand, ImageView imageView) {
51 | this.mImageView = imageView;
52 | this.isExpand = isExpand;
53 | setViewDimensions();
54 | }
55 |
56 | /**
57 | * 设置动画时间
58 | *
59 | * @param animationDuration 动画时间
60 | */
61 | public void setAnimationDuration(long animationDuration) {
62 | this.animationDuration = animationDuration;
63 | }
64 |
65 | /**
66 | * 获取subView的总高度
67 | * View.post()的runnable对象中的方法会在View的measure、layout等事件后触发
68 | */
69 | private void setViewDimensions() {
70 | layoutView.post(new Runnable() {
71 | @Override
72 | public void run() {
73 | if (viewHeight <= 0) {
74 | viewHeight = layoutView.getMeasuredHeight();
75 | }
76 | setViewHeight(layoutView, isExpand ? viewHeight : 0);
77 | }
78 | });
79 | }
80 |
81 |
82 | public static void setViewHeight(View view, int height) {
83 | final ViewGroup.LayoutParams params = view.getLayoutParams();
84 | params.height = height;
85 | view.requestLayout();
86 | }
87 |
88 | /**
89 | * 切换动画实现
90 | */
91 | private void animateToggle(long animationDuration) {
92 | ValueAnimator heightAnimation = isExpand ?
93 | ValueAnimator.ofFloat(0f, viewHeight) : ValueAnimator.ofFloat(viewHeight, 0f);
94 | heightAnimation.setDuration(animationDuration / 2);
95 | heightAnimation.setStartDelay(animationDuration / 2);
96 |
97 | heightAnimation.addUpdateListener(new ValueAnimator.AnimatorUpdateListener() {
98 | @Override
99 | public void onAnimationUpdate(ValueAnimator animation) {
100 | int value = (int) (float) animation.getAnimatedValue();
101 | setViewHeight(layoutView, value);
102 | if (value == viewHeight || value == 0) {
103 | lock = false;
104 | }
105 | }
106 | });
107 |
108 | heightAnimation.start();
109 | lock = true;
110 | }
111 |
112 | public boolean isExpand() {
113 | return isExpand;
114 | }
115 |
116 | /**
117 | * 折叠view
118 | */
119 | public void collapse() {
120 | isExpand = false;
121 | animateToggle(animationDuration);
122 | }
123 |
124 | /**
125 | * 展开view
126 | */
127 | public void expand() {
128 | isExpand = true;
129 | animateToggle(animationDuration);
130 | }
131 |
132 | public void toggleExpand() {
133 | if (lock) {
134 | return;
135 | }
136 | if (isExpand) {
137 | ObjectAnimator.ofFloat(mImageView, View.ROTATION.getName(), -180, 0).start();
138 | collapse();
139 | } else {
140 | ObjectAnimator.ofFloat(mImageView, View.ROTATION.getName(), 180, 0).start();
141 | expand();
142 | }
143 | }
144 | }
145 |
--------------------------------------------------------------------------------
/app/src/main/java/com/example/apple/keyboarddemo/expand/ExpandTextView.java:
--------------------------------------------------------------------------------
1 | package com.example.apple.keyboarddemo.expand;
2 |
3 | import android.animation.Animator;
4 | import android.animation.ValueAnimator;
5 | import android.content.Context;
6 | import android.graphics.drawable.Drawable;
7 | import android.support.annotation.Nullable;
8 | import android.support.v4.content.ContextCompat;
9 | import android.text.TextUtils;
10 | import android.util.AttributeSet;
11 | import android.util.Log;
12 | import android.view.LayoutInflater;
13 | import android.view.MotionEvent;
14 | import android.view.View;
15 | import android.widget.LinearLayout;
16 | import android.widget.TextView;
17 | import com.example.apple.keyboarddemo.R;
18 |
19 | /**
20 | * Created by apple on 2019/7/23.
21 | * description: 可折叠的TextView
22 | */
23 | public class ExpandTextView extends LinearLayout implements View.OnClickListener {
24 | private TextView mTvContent,mTvExpand;
25 |
26 | /*是否有重新绘制*/
27 | private boolean mRelayout;
28 |
29 | /*默认收起*/
30 | private boolean mCollapsed = true;
31 |
32 | /*动画执行时间*/
33 | private int mAnimationDuration = 300;
34 | /*是否正在执行动画*/
35 | private boolean mAnimating;
36 |
37 | /*设置内容最大行数,超过隐藏*/
38 | private int mMaxCollapsedLines = 2;
39 |
40 | /*这个linerlayout容器的高度*/
41 | private int mCollapsedHeight;
42 |
43 | /*内容tv真实高度(含padding)*/
44 | private int mTextHeightWithMaxLines;
45 |
46 | /*内容tvMarginTopAmndBottom高度*/
47 | private int mMarginBetweenTxtAndBottom;
48 |
49 | /*展开图片*/
50 | private Drawable mExpandDrawable;
51 | /*收起图片*/
52 | private Drawable mCollapseDrawable;
53 |
54 | public ExpandTextView(Context context) {
55 | this(context,null);
56 | }
57 |
58 | public ExpandTextView(Context context, @Nullable AttributeSet attrs) {
59 | this(context, attrs,0);
60 | }
61 |
62 | public ExpandTextView(Context context, @Nullable AttributeSet attrs, int defStyleAttr) {
63 | super(context, attrs, defStyleAttr);
64 | init(attrs);
65 | }
66 |
67 | /**
68 | * 设置orientation 必须为垂直
69 | * @param orientation
70 | */
71 | @Override
72 | public void setOrientation(int orientation) {
73 | if (LinearLayout.HORIZONTAL == orientation) {
74 | throw new IllegalArgumentException("ExpandableTextView only supports Vertical Orientation.");
75 | }
76 | super.setOrientation(orientation);
77 | }
78 |
79 | /**
80 | * 依据参数属性 设置排列方式
81 | * @param attrs
82 | */
83 | private void init(AttributeSet attrs) {
84 | Log.e("expand", "init: 初始化了" );
85 | mExpandDrawable = ContextCompat.getDrawable(getContext(), R.mipmap.ic_expand_down);
86 | mExpandDrawable.setBounds(0,0,25,25);
87 | mCollapseDrawable = ContextCompat.getDrawable(getContext(), R.mipmap.ic_expand_up);
88 | mCollapseDrawable.setBounds(0,0,25,25);
89 | setOrientation(VERTICAL);
90 | // 在加载前设置不可见...
91 | setVisibility(GONE);
92 | }
93 |
94 | /**
95 | * 渲染完成后初始化view
96 | */
97 | @Override
98 | protected void onFinishInflate() {
99 | super.onFinishInflate();
100 | initViews();
101 | }
102 |
103 |
104 | @Override
105 | protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
106 | // 如果没有改变则返回
107 | if (!mRelayout || getVisibility() == View.GONE){
108 | super.onMeasure(widthMeasureSpec, heightMeasureSpec);
109 | return;
110 | }
111 | mRelayout = false;
112 | mTvExpand.setVisibility(GONE);
113 | mTvContent.setMaxLines(Integer.MAX_VALUE);
114 | super.onMeasure(widthMeasureSpec, heightMeasureSpec);
115 |
116 | //如果内容真实行数小于等于最大行数,不处理
117 | if (mTvContent.getLineCount() <= mMaxCollapsedLines) {
118 | return;
119 | }
120 | // 获取内容tv真实高度(含padding)进行保存
121 | mTextHeightWithMaxLines = getRealTextViewHeight(mTvContent);
122 |
123 | // 如果是收起状态,重新设置最大行数
124 | if (mCollapsed) {
125 | mTvContent.setMaxLines(mMaxCollapsedLines);
126 | }
127 | mTvExpand.setVisibility(View.VISIBLE);
128 | super.onMeasure(widthMeasureSpec, heightMeasureSpec);
129 |
130 | if (mCollapsed) {
131 | // Gets the margin between the TextView's bottom and the ViewGroup's bottom
132 | mTvContent.post(new Runnable() {
133 | @Override
134 | public void run() {
135 | mMarginBetweenTxtAndBottom = getHeight() - mTvContent.getHeight();
136 | }
137 | });
138 | // 保存这个容器的测量高度
139 | mCollapsedHeight = getMeasuredHeight();
140 | }
141 |
142 | }
143 |
144 | @Override
145 | public boolean onInterceptTouchEvent(MotionEvent ev) {
146 | // 当动画还在执行状态时,拦截事件,不让child处理
147 | return mAnimating;
148 | }
149 |
150 | private void initViews() {
151 | LayoutInflater.from(getContext()).inflate(R.layout.textview_expand_shink,this);
152 | mTvContent = findViewById(R.id.expand_content);
153 | mTvContent.setOnClickListener(this);
154 | mTvExpand = findViewById(R.id.expand_collapse);
155 | mTvExpand.setText(mCollapsed ? "全文" : "收起");
156 | mTvExpand.setCompoundDrawables(null, null, mCollapsed ? mExpandDrawable : mCollapseDrawable, null);
157 | mTvExpand.setOnClickListener(this);
158 | }
159 |
160 | /**
161 | * 点击事件
162 | * @param v
163 | */
164 | @Override
165 | public void onClick(View v) {
166 | if (mTvExpand.getVisibility() != VISIBLE){
167 | return;
168 | }
169 | mCollapsed = !mCollapsed;
170 | mTvExpand.setText(mCollapsed ? "全文" : "收起");
171 | mTvExpand.setCompoundDrawables(null, null, mCollapsed ? mExpandDrawable : mCollapseDrawable, null);
172 | // 执行展开/收起动画
173 | mAnimating = true;
174 | ValueAnimator valueAnimator;
175 | if (mCollapsed) {
176 | valueAnimator = ValueAnimator.ofInt(getHeight(), mCollapsedHeight);
177 | } else {
178 | valueAnimator = ValueAnimator.ofInt(getHeight(), getHeight() +
179 | mTextHeightWithMaxLines - mTvContent.getHeight());
180 | }
181 | valueAnimator.addUpdateListener(new ValueAnimator.AnimatorUpdateListener(){
182 | @Override
183 | public void onAnimationUpdate(ValueAnimator valueAnimator) {
184 | int animatedValue = (int) valueAnimator.getAnimatedValue();
185 | mTvContent.setMaxHeight(animatedValue - mMarginBetweenTxtAndBottom);
186 | getLayoutParams().height = animatedValue;
187 | requestLayout();
188 | }
189 | });
190 | valueAnimator.addListener(new Animator.AnimatorListener() {
191 | @Override
192 | public void onAnimationStart(Animator animator) {
193 |
194 | }
195 | @Override
196 | public void onAnimationEnd(Animator animator) {
197 | // 动画结束后发送结束的信号
198 | /// clear the animation flag
199 | mAnimating = false;
200 | if (mCollapsed){
201 | mTvContent.setMaxLines(mMaxCollapsedLines);
202 | mTvContent.setEllipsize(TextUtils.TruncateAt.END);
203 | }
204 | }
205 | @Override
206 | public void onAnimationCancel(Animator animator) {
207 |
208 | }
209 | @Override
210 | public void onAnimationRepeat(Animator animator) {
211 |
212 | }
213 | });
214 | valueAnimator.setDuration(mAnimationDuration);
215 | valueAnimator.start();
216 | }
217 |
218 |
219 |
220 | /**
221 | * 设置内容
222 | * @param text
223 | */
224 | public void setText( CharSequence text) {
225 | mRelayout = true;
226 | mTvContent.setText(text);
227 | setVisibility(TextUtils.isEmpty(text) ? View.GONE : View.VISIBLE);
228 | }
229 |
230 | /**
231 | * 获取内容
232 | * @return
233 | */
234 | public CharSequence getText() {
235 | if (mTvContent == null) {
236 | return "";
237 | }
238 | return mTvContent.getText();
239 | }
240 |
241 | /**
242 | * 获取内容tv真实高度(含padding)
243 | * @param textView
244 | * @return
245 | */
246 | private static int getRealTextViewHeight( TextView textView) {
247 | int textHeight = textView.getLayout().getLineTop(textView.getLineCount());
248 | int padding = textView.getCompoundPaddingTop() + textView.getCompoundPaddingBottom();
249 | return textHeight + padding;
250 | }
251 | }
252 |
--------------------------------------------------------------------------------
/app/src/main/java/com/example/apple/keyboarddemo/focus/SoftFocusActivity.java:
--------------------------------------------------------------------------------
1 | package com.example.apple.keyboarddemo.focus;
2 |
3 | import android.content.Context;
4 | import android.os.IBinder;
5 | import android.support.v7.app.AppCompatActivity;
6 | import android.os.Bundle;
7 | import android.view.Gravity;
8 | import android.view.View;
9 | import android.view.inputmethod.InputMethodManager;
10 | import android.widget.EditText;
11 | import android.widget.LinearLayout;
12 |
13 | import com.example.apple.keyboarddemo.R;
14 | import com.example.apple.keyboarddemo.RedDotView;
15 | import com.example.apple.keyboarddemo.expand.ExpandTextView;
16 | import com.ycbjie.ycreddotviewlib.YCRedDotView;
17 |
18 | /**
19 | * created by apple on 2019/7/23
20 | *
21 | * 软键盘焦点活动
22 | */
23 | public class SoftFocusActivity extends AppCompatActivity {
24 | private View mRootView;
25 | private EditText mEtSend,mEtTheme;
26 | private ExpandTextView mExpandTextView;
27 | private LinearLayout mLinear;
28 | private RedDotView mRedDotView;
29 | @Override
30 | protected void onCreate(Bundle savedInstanceState) {
31 | super.onCreate(savedInstanceState);
32 | setContentView(R.layout.activity_soft_focus);
33 | initViews();
34 | initListener();
35 | initData();
36 | }
37 |
38 |
39 | private void initViews() {
40 | mRootView = findViewById(R.id.llRoot);
41 | mEtSend = findViewById(R.id.etSend);
42 | mEtTheme = findViewById(R.id.etTheme);
43 | mExpandTextView = findViewById(R.id.expandTextView);
44 | mLinear = findViewById(R.id.linear);
45 | mRedDotView = findViewById(R.id.redDot);
46 | mRedDotView.setBadgeCount(0);
47 | }
48 |
49 |
50 | private void initListener() {
51 | mRootView.setOnClickListener(new View.OnClickListener() {
52 | @Override
53 | public void onClick(View v) {
54 | if (getCurrentFocus() != null){
55 | mEtSend.clearFocus();
56 | mEtTheme.clearFocus();
57 | hideKeyboard(mRootView,getCurrentFocus().getWindowToken());
58 | }
59 | }
60 | });
61 | }
62 |
63 |
64 | private void initData() {
65 | mEtSend.requestFocus();
66 | mEtSend.setSelection(mEtSend.getText().toString().length());
67 | mExpandTextView.setText("我是谁我是谁我是谁我是谁我是谁我是谁我是谁我是谁我是谁我是谁我是谁我是谁我是谁我是谁我是谁我是谁我是谁我是谁我是谁我是谁我是谁我是谁我是谁我是谁我是谁我是谁我是谁我是谁我是谁我是谁我是谁我是谁我是谁");
68 | }
69 |
70 | private void initRedDot(){
71 |
72 | }
73 |
74 |
75 |
76 | private void hideKeyboard(final View view, IBinder windowToken) {
77 | if(windowToken == null){
78 | return;
79 | }
80 | InputMethodManager inputMethodManager = (InputMethodManager) view.getContext().getApplicationContext()
81 | .getSystemService(Context.INPUT_METHOD_SERVICE);
82 | if (inputMethodManager == null){
83 | return;
84 | }
85 | boolean active = inputMethodManager.isActive();
86 | if (active) {
87 | inputMethodManager.hideSoftInputFromWindow(windowToken, 0);
88 | }
89 | }
90 | }
91 |
--------------------------------------------------------------------------------
/app/src/main/java/com/example/apple/keyboarddemo/keyboard_num/CustomNumKeyboardView.java:
--------------------------------------------------------------------------------
1 | package com.example.apple.keyboarddemo.keyboard_num;
2 |
3 | import android.content.Context;
4 | import android.graphics.Canvas;
5 | import android.graphics.Paint;
6 | import android.graphics.Rect;
7 | import android.graphics.Typeface;
8 | import android.graphics.drawable.Drawable;
9 | import android.inputmethodservice.Keyboard;
10 | import android.inputmethodservice.KeyboardView;
11 | import android.util.AttributeSet;
12 |
13 | import com.example.apple.keyboarddemo.R;
14 |
15 | import java.util.Iterator;
16 |
17 | /**
18 | * @author crazyZhangxl on 2018/10/17.
19 | * Describe: 自定义基本使用技巧 ------ 有待强化一些技能哦哦
20 | */
21 | public class CustomNumKeyboardView
22 | extends KeyboardView
23 | {
24 | private Context mContext;
25 | private Keyboard mKeyboard;
26 | private boolean mConFirmBtnEnabled = true;
27 |
28 |
29 | public CustomNumKeyboardView(Context paramContext, AttributeSet paramAttributeSet)
30 | {
31 | super(paramContext, paramAttributeSet);
32 | this.mContext = paramContext;
33 | }
34 |
35 | public CustomNumKeyboardView(Context paramContext, AttributeSet paramAttributeSet, int paramInt)
36 | {
37 | super(paramContext, paramAttributeSet, paramInt);
38 | this.mContext = paramContext;
39 | }
40 |
41 | private void drawIcon(Canvas paramCanvas, Keyboard.Key paramKey)
42 | {
43 | if (paramKey.icon != null)
44 | {
45 | paramKey.icon.setBounds(
46 | paramKey.x + (paramKey.width - paramKey.icon.getIntrinsicWidth()) / 2,
47 | paramKey.y + (paramKey.height - paramKey.icon.getIntrinsicHeight()) / 2,
48 | paramKey.x + (paramKey.width - paramKey.icon.getIntrinsicWidth()) / 2 + paramKey.icon.getIntrinsicWidth(),
49 | paramKey.y + (paramKey.height - paramKey.icon.getIntrinsicHeight()) / 2 + paramKey.icon.getIntrinsicHeight());
50 | paramKey.icon.draw(paramCanvas);
51 | }
52 | }
53 |
54 | // 绘制背景
55 | private void drawKeyBackground(int paramInt, Canvas paramCanvas, Keyboard.Key paramKey)
56 | {
57 | Drawable localDrawable = this.mContext.getResources().getDrawable(paramInt);
58 | int[] arrayOfInt = paramKey.getCurrentDrawableState();
59 | if (paramKey.codes[0] != 0) {
60 | localDrawable.setState(arrayOfInt);
61 | }
62 | localDrawable.setBounds(paramKey.x, paramKey.y,
63 | paramKey.x + paramKey.width,
64 | paramKey.y + paramKey.height);
65 | localDrawable.draw(paramCanvas);
66 | }
67 |
68 | // 绘制文字
69 | private void drawText(Canvas paramCanvas, Keyboard.Key paramKey, float paramFloat, int paramInt, Typeface paramTypeface)
70 | {
71 | Rect localRect = new Rect();
72 | Paint localPaint = new Paint();
73 | localPaint.setTextAlign(Paint.Align.CENTER);
74 | localPaint.setAntiAlias(true);
75 | localPaint.setColor(paramInt);
76 | if (paramKey.label != null)
77 | {
78 | // 字体大小
79 | localPaint.setTextSize(paramFloat);
80 | localPaint.setTypeface(paramTypeface);
81 | localPaint.getTextBounds(paramKey.label.toString(), 0, paramKey.label.toString().length(), localRect);
82 | paramFloat = paramKey.x + paramKey.width / 2;
83 | paramInt = paramKey.y;
84 | int i = paramKey.height / 2;
85 | paramCanvas.drawText(paramKey.label.toString(), paramFloat, localRect.height() / 2 + (paramInt + i), localPaint);
86 | }
87 | }
88 |
89 | /*
90 | key值对应关系
91 | public static final int KEYCODE_SHIFT = -1;
92 | public static final int KEYCODE_MODE_CHANGE = -2;
93 | public static final int KEYCODE_CANCEL = -3;
94 | public static final int KEYCODE_DONE = -4;
95 | public static final int KEYCODE_DELETE = -5;
96 | public static final int KEYCODE_ALT = -6;*/
97 |
98 | @Override
99 | public void onDraw(Canvas paramCanvas)
100 | {
101 | super.onDraw(paramCanvas);
102 | this.mKeyboard = getKeyboard();
103 | Iterator localIterator = this.mKeyboard.getKeys().iterator();
104 | while (localIterator.hasNext())
105 | {
106 | Keyboard.Key localKey = (Keyboard.Key)localIterator.next();
107 | if (localKey.codes[0] == -5)
108 | {
109 | // 绘制删除键
110 | drawKeyBackground(R.drawable.keyboard_delete_btn_bg, paramCanvas, localKey);
111 | drawIcon(paramCanvas, localKey);
112 | } else if (localKey.codes[0] == -4)
113 | {
114 | // 绘制确认键
115 | if (this.mConFirmBtnEnabled) {
116 | drawKeyBackground(R.drawable.keyboard_cofirm_btn_bg, paramCanvas, localKey);
117 | drawText(paramCanvas, localKey, getResources().getDimension(R.dimen.fontsize36), -1, Typeface.DEFAULT);
118 | }else {
119 | drawKeyBackground(R.drawable.keyboard_cofirm_btn_bg, paramCanvas, localKey);
120 | drawText(paramCanvas, localKey, getResources().getDimension(R.dimen.fontsize36), -7829368, Typeface.DEFAULT);
121 | }
122 | }else if (localKey.codes[0] == -3){
123 | // 绘制键盘图标
124 | drawKeyBackground(R.drawable.keyboard_btn_normal_bg, paramCanvas, localKey);
125 | drawIcon(paramCanvas, localKey);
126 | } else {
127 | // 绘制其他的数字 这里和 xml是对应的关系
128 | drawKeyBackground(R.drawable.keyboard_btn_normal_bg, paramCanvas, localKey);
129 | drawText(paramCanvas, localKey, getResources().getDimension(R.dimen.fontsize36), R.color.color_red, Typeface.DEFAULT);
130 | }
131 | }
132 | }
133 |
134 | public boolean isConfirmBtnEnabled()
135 | {
136 | return this.mConFirmBtnEnabled;
137 | }
138 |
139 |
140 | public void setConfirmBtnEnabled(boolean paramBoolean)
141 | {
142 | this.mConFirmBtnEnabled = paramBoolean;
143 | invalidate();
144 | }
145 | }
146 |
--------------------------------------------------------------------------------
/app/src/main/java/com/example/apple/keyboarddemo/keyboard_num/KeyBoardNumberActivity.java:
--------------------------------------------------------------------------------
1 | package com.example.apple.keyboarddemo.keyboard_num;
2 |
3 | import android.os.Bundle;
4 | import android.support.v7.app.AppCompatActivity;
5 | import android.widget.EditText;
6 | import android.widget.Toast;
7 |
8 | import com.example.apple.keyboarddemo.R;
9 |
10 | /**
11 | * @author crazyZhangxl on 2018-10-18 8:36:33.
12 | * Describe:
13 | */
14 |
15 | public class KeyBoardNumberActivity extends AppCompatActivity implements KeyboardNumUtil.NumKeyboardListener {
16 | private EditText mEditText;
17 | private CustomNumKeyboardView mCustomNumKeyboardView;
18 | private KeyboardNumUtil mKeyboardNumUtil;
19 | @Override
20 | protected void onCreate(Bundle savedInstanceState) {
21 | super.onCreate(savedInstanceState);
22 | setContentView(R.layout.activity_keyboard_number);
23 |
24 | mEditText = findViewById(R.id.editText);
25 | mCustomNumKeyboardView = findViewById(R.id.keyboardView);
26 | mKeyboardNumUtil = new KeyboardNumUtil(this,mCustomNumKeyboardView);
27 | mKeyboardNumUtil.attachEditText(mEditText);
28 | mKeyboardNumUtil.setNumKeyboardListener(this);
29 | mKeyboardNumUtil.showNumKeyboard();
30 | }
31 |
32 | @Override
33 | public void onClose() {
34 | Toast.makeText(this, "关闭软键盘", Toast.LENGTH_SHORT).show();
35 | }
36 |
37 | @Override
38 | public void onConfirm() {
39 | Toast.makeText(this, "确定", Toast.LENGTH_SHORT).show();
40 | }
41 |
42 | @Override
43 | public void onNumberChanged(String paramString) {
44 | // 键盘录入时
45 | }
46 | }
47 |
--------------------------------------------------------------------------------
/app/src/main/java/com/example/apple/keyboarddemo/keyboard_num/KeyboardNumUtil.java:
--------------------------------------------------------------------------------
1 | package com.example.apple.keyboarddemo.keyboard_num;
2 |
3 | import android.content.Context;
4 | import android.inputmethodservice.Keyboard;
5 | import android.inputmethodservice.KeyboardView;
6 | import android.view.inputmethod.InputMethodManager;
7 | import android.widget.EditText;
8 |
9 | import com.example.apple.keyboarddemo.R;
10 |
11 | import java.lang.reflect.InvocationTargetException;
12 | import java.lang.reflect.Method;
13 |
14 | /**
15 | * @author crazyZhangxl on 2018/10/18.
16 | * Describe:
17 | */
18 | public class KeyboardNumUtil {
19 | private int keyboardLayout;
20 | private Context mContext;
21 | private EditText mEditText;
22 | private KeyboardView mKeyboardView;
23 | private InputMethodManager mInputMethodManager;
24 | private NumKeyboardListener mNumKeyboardListener;
25 | private KeyboardView.OnKeyboardActionListener mOnKeyboardActionListener = new KeyboardView.OnKeyboardActionListener() {
26 | @Override
27 | public void onPress(int primaryCode) {
28 |
29 | }
30 |
31 | @Override
32 | public void onRelease(int primaryCode) {
33 |
34 | }
35 |
36 | @Override
37 | public void onKey(int primaryCode, int[] keyCodes) {
38 | if (primaryCode == Keyboard.KEYCODE_CANCEL) {
39 | // 取消 -------- 进行隐藏的效果 ---------
40 | // --------------------------
41 | if (mNumKeyboardListener != null){
42 | mNumKeyboardListener.onClose();
43 | }
44 | } else if (primaryCode == Keyboard.KEYCODE_DELETE) {
45 | // 回退 --------- 删除该数据 ---------
46 | if (mEditText != null){
47 | int start = mEditText.getSelectionStart();
48 | if (start >= 1){
49 | mEditText.getText().delete(start-1,start);
50 | }
51 | }
52 | } else if (primaryCode == Keyboard.KEYCODE_DONE){
53 | // 确定提交--------------
54 | if (mNumKeyboardListener != null){
55 | mNumKeyboardListener.onConfirm();
56 | }
57 | }else {
58 | // 具体的数值
59 | String num = Character.toString((char) primaryCode);
60 | if (mEditText != null){
61 | int length = mEditText.getText().toString().length();
62 | mEditText.getText().insert(length,num);
63 | }
64 | if (mNumKeyboardListener != null){
65 | mNumKeyboardListener.onNumberChanged(num);
66 | }
67 | }
68 | }
69 |
70 | @Override
71 | public void onText(CharSequence text) {
72 |
73 | }
74 |
75 | @Override
76 | public void swipeLeft() {
77 |
78 | }
79 |
80 | @Override
81 | public void swipeRight() {
82 |
83 | }
84 |
85 | @Override
86 | public void swipeDown() {
87 |
88 | }
89 |
90 | @Override
91 | public void swipeUp() {
92 |
93 | }
94 | };
95 |
96 | public KeyboardNumUtil(Context mContext, KeyboardView keyboardView){
97 | this.mContext = mContext;
98 | this.mKeyboardView = keyboardView;
99 | this.mInputMethodManager = ((InputMethodManager)this.mContext.getSystemService(Context.INPUT_METHOD_SERVICE));
100 | }
101 |
102 | public void attachEditText(EditText editText){
103 | this.mEditText = editText;
104 | }
105 |
106 | /**
107 | * 展示keyboard
108 | */
109 | public void showNumKeyboard(){
110 | mKeyboardView.setKeyboard(new Keyboard(this.mContext, R.xml.keyboard_num_woith_dot));
111 | mKeyboardView.setEnabled(true);
112 | mKeyboardView.setPreviewEnabled(false);
113 | mKeyboardView.setOnKeyboardActionListener(this.mOnKeyboardActionListener);
114 | }
115 |
116 |
117 | /**
118 | * 隐藏系统的软键盘
119 | */
120 | private void hideSystemKeyboard(){
121 | Method localMethod = null;
122 | try {
123 | localMethod = EditText.class.getMethod("setShowSoftInputOnFocus", Boolean.TYPE);
124 | localMethod.setAccessible(true);
125 | try {
126 | localMethod.invoke(this.mEditText, Boolean.FALSE);
127 | } catch (IllegalAccessException e) {
128 | e.printStackTrace();
129 | } catch (InvocationTargetException e) {
130 | e.printStackTrace();
131 | }
132 | this.mInputMethodManager.hideSoftInputFromWindow(this.mEditText.getWindowToken(), 0);
133 | } catch (NoSuchMethodException e) {
134 | e.printStackTrace();
135 | }
136 |
137 | }
138 |
139 | public void setNumKeyboardListener(NumKeyboardListener numKeyboardListener){
140 | this.mNumKeyboardListener = numKeyboardListener;
141 | }
142 |
143 |
144 | public interface NumKeyboardListener{
145 | void onClose();
146 | void onConfirm();
147 | void onNumberChanged(String paramString);
148 | }
149 | }
150 |
--------------------------------------------------------------------------------
/app/src/main/java/com/example/apple/keyboarddemo/shelter_aty/ShelterActivity.java:
--------------------------------------------------------------------------------
1 | package com.example.apple.keyboarddemo.shelter_aty;
2 |
3 | import android.support.v7.app.AppCompatActivity;
4 | import android.os.Bundle;
5 | import android.view.WindowManager;
6 |
7 | import com.example.apple.keyboarddemo.R;
8 |
9 | /**
10 | * @author crazyZhangxl on 2018-10-25 16:17:06.
11 | * Describe: 活动中软键盘的遮挡问题探究
12 | */
13 |
14 | public class ShelterActivity extends AppCompatActivity {
15 |
16 | @Override
17 | protected void onCreate(Bundle savedInstanceState) {
18 | super.onCreate(savedInstanceState);
19 | getWindow().setSoftInputMode(WindowManager.LayoutParams.SOFT_INPUT_STATE_ALWAYS_HIDDEN);
20 | setContentView(R.layout.activity_shelter);
21 | }
22 | }
23 |
--------------------------------------------------------------------------------
/app/src/main/java/com/example/apple/keyboarddemo/top_dismiss/TopDismissFirstActivity.java:
--------------------------------------------------------------------------------
1 | package com.example.apple.keyboarddemo.top_dismiss;
2 |
3 | import android.support.v7.app.AppCompatActivity;
4 | import android.os.Bundle;
5 |
6 | import com.example.apple.keyboarddemo.R;
7 |
8 | /**
9 | * @author crazyZhangxl on 2019-1-3 20:18:36.
10 | * Describe: 什么也不添加
11 | */
12 |
13 | public class TopDismissFirstActivity extends AppCompatActivity {
14 | @Override
15 | protected void onCreate(Bundle savedInstanceState) {
16 | super.onCreate(savedInstanceState);
17 | setContentView(R.layout.activity_top_dismiss_first);
18 | }
19 | }
20 |
--------------------------------------------------------------------------------
/app/src/main/java/com/example/apple/keyboarddemo/top_dismiss/TopDismissSecondActivity.java:
--------------------------------------------------------------------------------
1 | package com.example.apple.keyboarddemo.top_dismiss;
2 |
3 | import android.support.v7.app.AppCompatActivity;
4 | import android.os.Bundle;
5 |
6 | import com.example.apple.keyboarddemo.R;
7 |
8 | /**
9 | * @author crazyZhangxl on 2019-1-3 20:19:30.
10 | * Describe:
11 | */
12 |
13 | public class TopDismissSecondActivity extends AppCompatActivity {
14 |
15 | @Override
16 | protected void onCreate(Bundle savedInstanceState) {
17 | super.onCreate(savedInstanceState);
18 | setContentView(R.layout.activity_top_dismiss_first);
19 | }
20 | }
21 |
--------------------------------------------------------------------------------
/app/src/main/java/com/example/apple/keyboarddemo/top_dismiss/TopDismissThirdActivity.java:
--------------------------------------------------------------------------------
1 | package com.example.apple.keyboarddemo.top_dismiss;
2 |
3 | import android.support.v7.app.AppCompatActivity;
4 | import android.os.Bundle;
5 | import android.view.WindowManager;
6 |
7 | import com.example.apple.keyboarddemo.R;
8 |
9 | /**
10 | * @author crazyZhangxl on 2019-1-3 20:29:38.
11 | * Describe:
12 | */
13 |
14 | public class TopDismissThirdActivity extends AppCompatActivity {
15 |
16 | @Override
17 | protected void onCreate(Bundle savedInstanceState) {
18 | super.onCreate(savedInstanceState);
19 | getWindow().setSoftInputMode(WindowManager.LayoutParams.SOFT_INPUT_STATE_ALWAYS_HIDDEN);
20 | setContentView(R.layout.activity_top_dismiss_first);
21 | }
22 | }
23 |
--------------------------------------------------------------------------------
/app/src/main/java/com/example/apple/keyboarddemo/topic/BottomDialog.java:
--------------------------------------------------------------------------------
1 | package com.example.apple.keyboarddemo.topic;
2 |
3 | import android.content.Context;
4 | import android.content.DialogInterface;
5 | import android.support.annotation.NonNull;
6 | import android.support.design.widget.BottomSheetBehavior;
7 | import android.support.design.widget.BottomSheetDialog;
8 | import android.support.design.widget.CoordinatorLayout;
9 | import android.view.View;
10 | import android.view.ViewGroup;
11 | import android.view.Window;
12 | import android.view.WindowManager;
13 |
14 | /**
15 | * @author crazyZhangxl on 2018/10/25.
16 | * Describe:
17 | */
18 | public class BottomDialog extends BottomSheetDialog {
19 |
20 | private BottomSheetBehavior behavior;
21 |
22 | public BottomDialog(@NonNull Context context, int theme,boolean isTranslucentStatus) {
23 | super(context, theme);
24 | Window window = getWindow();
25 | if (window != null) {
26 | window.setSoftInputMode(WindowManager.LayoutParams.SOFT_INPUT_ADJUST_RESIZE |
27 | WindowManager.LayoutParams.SOFT_INPUT_STATE_HIDDEN);
28 | if (isTranslucentStatus) {
29 | window.addFlags(WindowManager.LayoutParams.FLAG_TRANSLUCENT_STATUS);
30 | }
31 | }
32 | }
33 |
34 | @Override
35 | public void setContentView(View view) {
36 | super.setContentView(view);
37 | initialize(view);
38 | setOnDismissListener(new DialogInterface.OnDismissListener() {
39 | @Override
40 | public void onDismiss(DialogInterface dialog) {
41 | if (behavior != null) {
42 | behavior.setState(BottomSheetBehavior.STATE_COLLAPSED);
43 | }
44 | }
45 | });
46 | }
47 |
48 | @Override
49 | public void show() {
50 | super.show();
51 | behavior.setState(BottomSheetBehavior.STATE_EXPANDED);
52 | }
53 |
54 | private void initialize(final View view) {
55 | ViewGroup parent = (ViewGroup) view.getParent();
56 | CoordinatorLayout.LayoutParams params = (CoordinatorLayout.LayoutParams) parent.getLayoutParams();
57 | behavior = (BottomSheetBehavior) params.getBehavior();
58 | if (behavior == null) {
59 | return;
60 | }
61 | behavior.setBottomSheetCallback(new BottomSheetBehavior.BottomSheetCallback() {
62 | @Override
63 | public void onStateChanged(@NonNull View bottomSheet, int newState) {
64 | if (newState == BottomSheetBehavior.STATE_HIDDEN) {
65 | dismiss();
66 | behavior.setState(BottomSheetBehavior.STATE_COLLAPSED);
67 | }
68 | }
69 |
70 | @Override
71 | public void onSlide(@NonNull View bottomSheet, float slideOffset) {
72 | }
73 | });
74 | }
75 | }
76 |
--------------------------------------------------------------------------------
/app/src/main/java/com/example/apple/keyboarddemo/topic/BottomSheetBar.java:
--------------------------------------------------------------------------------
1 | package com.example.apple.keyboarddemo.topic;
2 |
3 | import android.annotation.SuppressLint;
4 | import android.content.Context;
5 | import android.content.DialogInterface;
6 | import android.content.Intent;
7 | import android.text.Editable;
8 | import android.text.TextUtils;
9 | import android.text.TextWatcher;
10 | import android.view.LayoutInflater;
11 | import android.view.MotionEvent;
12 | import android.view.View;
13 | import android.view.inputmethod.InputMethodManager;
14 | import android.widget.Button;
15 | import android.widget.CheckBox;
16 | import android.widget.EditText;
17 | import android.widget.FrameLayout;
18 | import android.widget.ImageButton;
19 |
20 | import com.example.apple.keyboarddemo.R;
21 |
22 | /**
23 | * @author crazyZhangxl on 2018/10/25.
24 | * Describe:
25 | */
26 | public class BottomSheetBar {
27 |
28 | private View mRootView;
29 | private EditText mEditText;
30 | private Context mContext;
31 | private Button mBtnCommit;
32 | private BottomDialog mDialog;
33 | private FrameLayout mFrameLayout;
34 |
35 |
36 | private BottomSheetBar(Context context) {
37 | this.mContext = context;
38 | }
39 |
40 | @SuppressLint("InflateParams")
41 | public static BottomSheetBar delegation(Context context) {
42 | BottomSheetBar bar = new BottomSheetBar(context);
43 | bar.mRootView = LayoutInflater.from(context).inflate(R.layout.layout_bottom_sheet_comment_bar, null, false);
44 | bar.initView();
45 | return bar;
46 | }
47 |
48 | @SuppressLint("ClickableViewAccessibility")
49 | private void initView() {
50 | mFrameLayout = (FrameLayout) mRootView.findViewById(R.id.fl_face);
51 | mEditText = (EditText) mRootView.findViewById(R.id.et_comment);
52 |
53 | mBtnCommit = (Button) mRootView.findViewById(R.id.btn_comment);
54 | mBtnCommit.setEnabled(false);
55 |
56 | mDialog = new BottomDialog(mContext, R.style.BottomSheetEdit,false);
57 | mDialog.setContentView(mRootView);
58 |
59 | mDialog.setOnDismissListener(new DialogInterface.OnDismissListener() {
60 | @Override
61 | public void onDismiss(DialogInterface dialog) {
62 | closeKeyboard(mEditText);
63 | mFrameLayout.setVisibility(View.GONE);
64 | }
65 | });
66 |
67 | mEditText.addTextChangedListener(new TextWatcher() {
68 | @Override
69 | public void beforeTextChanged(CharSequence s, int start, int count, int after) {
70 |
71 | }
72 |
73 | @Override
74 | public void onTextChanged(CharSequence s, int start, int before, int count) {
75 |
76 | }
77 |
78 | @Override
79 | public void afterTextChanged(Editable s) {
80 | mBtnCommit.setEnabled(s.length() > 0);
81 | }
82 | });
83 |
84 | }
85 |
86 | public void showEmoji() {
87 |
88 | mEditText.setOnClickListener(new View.OnClickListener() {
89 | @Override
90 | public void onClick(View v) {
91 | mFrameLayout.setVisibility(View.GONE);
92 | }
93 | });
94 | }
95 |
96 | public void show(String hint) {
97 | mDialog.show();
98 | if (!"添加评论".equals(hint)) {
99 | mEditText.setHint(hint + " ");
100 | }
101 | mRootView.postDelayed(new Runnable() {
102 | @Override
103 | public void run() {
104 | showSoftKeyboard(mEditText);
105 | }
106 | }, 50);
107 | }
108 |
109 | public void dismiss() {
110 | closeKeyboard(mEditText);
111 | mDialog.dismiss();
112 | }
113 |
114 |
115 |
116 |
117 | public String getCommentText() {
118 | String str = mEditText.getText().toString();
119 | return TextUtils.isEmpty(str) ? "" : str.trim();
120 | }
121 |
122 | public Button getBtnCommit() {
123 | return mBtnCommit;
124 | }
125 |
126 |
127 |
128 |
129 | private void closeKeyboard(EditText view) {
130 | view.clearFocus();
131 | InputMethodManager imm = (InputMethodManager) view.getContext().getSystemService(Context.INPUT_METHOD_SERVICE);
132 | imm.hideSoftInputFromWindow(view.getWindowToken(), InputMethodManager.RESULT_UNCHANGED_SHOWN);
133 | }
134 |
135 | private void showSoftKeyboard(View view) {
136 | if (view == null) {
137 | return;
138 | }
139 | view.setFocusable(true);
140 | view.setFocusableInTouchMode(true);
141 | if (!view.isFocused()) {
142 | view.requestFocus();
143 | }
144 |
145 | InputMethodManager inputMethodManager = (InputMethodManager) view.getContext()
146 | .getSystemService(Context.INPUT_METHOD_SERVICE);
147 | inputMethodManager.showSoftInput(view, 0);
148 | }
149 |
150 | }
151 |
152 |
--------------------------------------------------------------------------------
/app/src/main/java/com/example/apple/keyboarddemo/topic/RichEditText.java:
--------------------------------------------------------------------------------
1 | package com.example.apple.keyboarddemo.topic;
2 |
3 | import android.content.ClipData;
4 | import android.content.ClipboardManager;
5 | import android.content.Context;
6 | import android.os.Parcel;
7 | import android.os.Parcelable;
8 | import android.support.annotation.NonNull;
9 | import android.support.v7.widget.AppCompatEditText;
10 | import android.text.Editable;
11 | import android.text.Selection;
12 | import android.text.Spannable;
13 | import android.text.SpannableString;
14 | import android.text.Spanned;
15 | import android.text.TextPaint;
16 | import android.text.TextUtils;
17 | import android.text.TextWatcher;
18 | import android.text.style.ForegroundColorSpan;
19 | import android.util.AttributeSet;
20 | import android.view.KeyEvent;
21 | import android.view.inputmethod.EditorInfo;
22 | import android.view.inputmethod.InputConnection;
23 | import android.view.inputmethod.InputConnectionWrapper;
24 |
25 |
26 | import java.util.logging.Logger;
27 | import java.util.regex.Matcher;
28 | import java.util.regex.Pattern;
29 |
30 |
31 | /**
32 | * 一个简单的富文本编辑器
33 | * 实现了@(AT)和##的Tag匹配功能,
34 | * 具有Tag删除判断,和光标定位判断;预防用户胡乱篡改
35 | *
36 | * @author qiujuer Email:qiujuer@live.cn
37 | * @version 1.0.0
38 | */
39 | @SuppressWarnings("all")
40 | public class RichEditText extends AppCompatEditText {
41 | public static final String MATCH_MENTION = "@([^@^\\s^:^,^;^','^';'^>^<]{1,})";//@([^@^\\s^:]{1,})([\\s\\:\\,\\;]{0,1})");//@.+?[\\s:]
42 | public static final String MATCH_TOPIC = "#.+?#";
43 | public static boolean DEBUG = false;
44 | private static final String TAG = RichEditText.class.getName();
45 | private final RichEditText.TagSpanTextWatcher mTagSpanTextWatcher = new RichEditText.TagSpanTextWatcher();
46 | private RichEditText.OnKeyArrivedListener mOnKeyArrivedListener;
47 |
48 | public RichEditText(Context context) {
49 | super(context);
50 | init();
51 | }
52 |
53 | public RichEditText(Context context, AttributeSet attrs) {
54 | super(context, attrs);
55 | init();
56 | }
57 |
58 | public RichEditText(Context context, AttributeSet attrs, int defStyleAttr) {
59 | super(context, attrs, defStyleAttr);
60 | init();
61 | }
62 |
63 | private void init() {
64 | addTextChangedListener(mTagSpanTextWatcher);
65 | }
66 |
67 | @Override
68 | public InputConnection onCreateInputConnection(EditorInfo outAttrs) {
69 | return new RichEditText.ZanyInputConnection(super.onCreateInputConnection(outAttrs), true);
70 | }
71 |
72 | @Override
73 | public void setText(CharSequence text, BufferType type) {
74 | Spannable spannable = new SpannableString(text);
75 | spannable = matchMention(spannable);
76 | spannable = matchTopic(spannable);
77 | super.setText(spannable, type);
78 | }
79 |
80 | @Override
81 | protected void onSelectionChanged(int selStart, int selEnd) {
82 | log("onSelectionChanged:" + selStart + " " + selEnd);
83 | Editable message = getText();
84 |
85 | if (selStart == selEnd) {
86 | RichEditText.TagSpan[] list = message.getSpans(selStart - 1, selStart, RichEditText.TagSpan.class);
87 | if (list.length > 0) {
88 | // Get first tag
89 | RichEditText.TagSpan span = list[0];
90 | int spanStart = message.getSpanStart(span);
91 | int spanEnd = message.getSpanEnd(span);
92 | log("onSelectionChanged#Yes:" + spanStart + " " + spanEnd);
93 | // Check index
94 | if (Math.abs(selStart - spanStart) > Math.abs(selStart - spanEnd)) {
95 | Selection.setSelection(message, spanEnd);
96 | replaceCacheTagSpan(message, span, false);
97 | return;
98 | } else {
99 | Selection.setSelection(message, spanStart);
100 | }
101 | }
102 | } else {
103 | RichEditText.TagSpan[] list = message.getSpans(selStart, selEnd, RichEditText.TagSpan.class);
104 | if (list.length == 0)
105 | return;
106 | int start = selStart;
107 | int end = selEnd;
108 | for (RichEditText.TagSpan span : list) {
109 | int spanStart = message.getSpanStart(span);
110 | int spanEnd = message.getSpanEnd(span);
111 |
112 | if (spanStart < start)
113 | start = spanStart;
114 |
115 | if (spanEnd > end)
116 | end = spanEnd;
117 | }
118 | if (start != selStart || end != selEnd) {
119 | Selection.setSelection(message, start, end);
120 | log("onSelectionChanged#No:" + start + " " + end);
121 | }
122 | }
123 |
124 | replaceCacheTagSpan(message, null, false);
125 | }
126 |
127 | @Override
128 | public boolean onTextContextMenuItem(int id) {
129 | // Handle the paste option
130 | if (id == android.R.id.paste) {
131 | // Handle to the clipboard service.
132 | ClipboardManager clipboard = (ClipboardManager) getContext().getSystemService(Context.CLIPBOARD_SERVICE);
133 | // Handle the data.
134 | if (clipboard.hasPrimaryClip()) {
135 | ClipData.Item item = clipboard.getPrimaryClip().getItemAt(0);
136 | if (item != null) {
137 | // Gets the clipboard date to string and do trim
138 | String paste = item.coerceToText(getContext()).toString().trim();
139 | // Check need space
140 | if (mTagSpanTextWatcher != null && mTagSpanTextWatcher.checkCommit(paste))
141 | paste = " " + paste;
142 | // Clear add span
143 | Spannable spannablePaste = new SpannableString(paste);
144 | spannablePaste = matchMention(spannablePaste);
145 | spannablePaste = matchTopic(spannablePaste);
146 | getText().replace(getSelectionStart(), getSelectionEnd(), spannablePaste);
147 | return true;
148 | }
149 | }
150 | }
151 | // Call super
152 | return super.onTextContextMenuItem(id);
153 | }
154 |
155 | public void setOnKeyArrivedListener(RichEditText.OnKeyArrivedListener listener) {
156 | mOnKeyArrivedListener = listener;
157 | }
158 |
159 | protected boolean callToMention() {
160 | RichEditText.OnKeyArrivedListener listener = mOnKeyArrivedListener;
161 | return listener == null || listener.onMentionKeyArrived(this);
162 | }
163 |
164 | protected boolean callToTopic() {
165 | RichEditText.OnKeyArrivedListener listener = mOnKeyArrivedListener;
166 | return listener == null || listener.onTopicKeyArrived(this);
167 | }
168 |
169 | private void replaceCacheTagSpan(Editable message, RichEditText.TagSpan span, boolean targetDelState) {
170 | if (mTagSpanTextWatcher != null) {
171 | mTagSpanTextWatcher.replaceSpan(message, span, targetDelState);
172 | }
173 | }
174 |
175 | private String filterDirty(String str) {
176 | return str.replace("#", "").replace("@", "").replace(" ", "");
177 | }
178 |
179 | private void replaceLastChar(@NonNull String chr, SpannableString spannable) {
180 | Editable msg = getText();
181 | int selStart = getSelectionStart();
182 | int selEnd = getSelectionEnd();
183 |
184 | int selStartBefore = selStart - 1;
185 | if (selStart == selEnd && selStart > 0
186 | && chr.equals(msg.subSequence(selStartBefore, selEnd).toString())
187 | && msg.getSpans(selStartBefore, selEnd, RichEditText.TagSpan.class).length == 0) {
188 | selStart = selStartBefore;
189 | }
190 |
191 | msg.replace(selStart >= 0 ? selStart : 0, selEnd >= 0 ? selEnd : 0, spannable);
192 | }
193 |
194 | /**
195 | * 添加提到字符串
196 | *
197 | * @param mentions 提及的人,不含@
198 | */
199 | @SuppressWarnings("unused")
200 | public void appendMention(String... mentions) {
201 | if (mentions == null || mentions.length == 0)
202 | return;
203 |
204 | String mentionStr = "";
205 |
206 | for (String mention : mentions) {
207 | if (mention == null || TextUtils.isEmpty(mention = mention.trim())
208 | || TextUtils.isEmpty(mention = filterDirty(mention)))
209 | continue;
210 | mentionStr += String.format("@%s ", mention);
211 | }
212 | if (TextUtils.isEmpty(mentionStr))
213 | return;
214 |
215 | SpannableString spannable = new SpannableString(mentionStr);
216 | RichEditText.matchMention(spannable);
217 |
218 | replaceLastChar("@", spannable);
219 | }
220 |
221 | /**
222 | * 添加话题字符串
223 | *
224 | * @param topics 话题,不含#
225 | */
226 | @SuppressWarnings("unused")
227 | public void appendTopic(String... topics) {
228 | if (topics == null || topics.length == 0) {
229 | return;
230 | }
231 |
232 | String topicStr = "";
233 |
234 | for (String topic : topics) {
235 | if (topic == null || TextUtils.isEmpty(topic = topic.trim())
236 | || TextUtils.isEmpty(topic = filterDirty(topic))) {
237 | continue;
238 | }
239 | topicStr += String.format("#%s# ", topic);
240 | }
241 | if (TextUtils.isEmpty(topicStr)) {
242 | return;
243 | }
244 |
245 | SpannableString spannable = new SpannableString(topicStr);
246 | RichEditText.matchTopic(spannable);
247 |
248 | replaceLastChar("#", spannable);
249 | }
250 |
251 |
252 | private class TagSpanTextWatcher implements TextWatcher {
253 | private RichEditText.TagSpan willDelSpan;
254 |
255 | void replaceSpan(Editable message, RichEditText.TagSpan span, boolean targetDelState) {
256 | if (span != null) {
257 | span.changeRemoveState(targetDelState, message);
258 | }
259 |
260 | if (willDelSpan != span) {
261 | // When different
262 | RichEditText.TagSpan cacheSpan = willDelSpan;
263 | if (cacheSpan != null) {
264 | cacheSpan.changeRemoveState(false, message);
265 | }
266 | willDelSpan = span;
267 | }
268 | }
269 |
270 | boolean checkKeyDel() {
271 | int selStart = getSelectionStart();
272 | int selEnd = getSelectionEnd();
273 | Editable message = getText();
274 | log("TagSpanTextWatcher#checkKeyDel:" + selStart + " " + selEnd);
275 | if (selStart == selEnd) {
276 | int start = selStart - 1;
277 | int count = 1;
278 |
279 | start = start < 0 ? 0 : start;
280 |
281 | int end = start + count;
282 | RichEditText.TagSpan[] list = message.getSpans(start, end, RichEditText.TagSpan.class);
283 |
284 | if (list.length > 0) {
285 | // Only get first
286 | final RichEditText.TagSpan span = list[0];
287 | final RichEditText.TagSpan cacheSpan = willDelSpan;
288 |
289 | if (span == cacheSpan) {
290 | if (span.willRemove)
291 | return true;
292 | else {
293 | span.changeRemoveState(true, message);
294 | return false;
295 | }
296 | }
297 | }
298 | }
299 | // Replace cache tag to null
300 | replaceSpan(message, null, false);
301 | return true;
302 | }
303 |
304 | boolean checkCommit(CharSequence s) {
305 | if (willDelSpan != null) {
306 | willDelSpan.willRemove = false;
307 | willDelSpan = null;
308 | return s != null && s.length() > 0 && !" ".equals(s.subSequence(0, 1));
309 | }
310 | return false;
311 | }
312 |
313 | @Override
314 | public void beforeTextChanged(CharSequence s, int start, int count, int after) {
315 |
316 | }
317 |
318 | @Override
319 | public void onTextChanged(CharSequence s, int start, int before, int count) {
320 |
321 | }
322 |
323 | @Override
324 | public void afterTextChanged(Editable s) {
325 | final RichEditText.TagSpan span = willDelSpan;
326 | log("TagSpanTextWatcher#willRemove#span:" + (span == null ? "null" : span.toString()));
327 | if (span != null && span.willRemove) {
328 | int start = s.getSpanStart(span);
329 | int end = s.getSpanEnd(span);
330 |
331 | // Remove the span
332 | s.removeSpan(span);
333 |
334 | // Remove the remaining emoticon text.
335 | if (start != end) {
336 | s.delete(start, end);
337 | }
338 | }
339 | }
340 | }
341 |
342 | public interface OnKeyArrivedListener {
343 | boolean onMentionKeyArrived(RichEditText editText);
344 |
345 | boolean onTopicKeyArrived(RichEditText text);
346 | }
347 |
348 | public static Spannable matchMention(Spannable spannable) {
349 | String text = spannable.toString();
350 |
351 | Pattern pattern = Pattern.compile(MATCH_MENTION);
352 | Matcher matcher = pattern.matcher(text);
353 |
354 | while (matcher.find()) {
355 | String str = matcher.group();
356 | int matcherStart = matcher.start();
357 | int matcherEnd = matcher.end();
358 | spannable.setSpan(new RichEditText.TagSpan(str), matcherStart, matcherEnd, Spanned.SPAN_EXCLUSIVE_EXCLUSIVE);
359 | log("matchMention:" + str + " " + matcherStart + " " + matcherEnd);
360 | }
361 |
362 | return spannable;
363 | }
364 |
365 | public static Spannable matchTopic(Spannable spannable) {
366 | String text = spannable.toString();
367 |
368 | Pattern pattern = Pattern.compile(MATCH_TOPIC);
369 | Matcher matcher = pattern.matcher(text);
370 |
371 | while (matcher.find()) {
372 | String str = matcher.group();
373 | int matcherStart = matcher.start();
374 | int matcherEnd = matcher.end();
375 | spannable.setSpan(new RichEditText.TagSpan(str), matcherStart, matcherEnd, Spanned.SPAN_EXCLUSIVE_EXCLUSIVE);
376 | log("matchTopic:" + str + " " + matcherStart + " " + matcherEnd);
377 | }
378 |
379 | return spannable;
380 | }
381 |
382 | private static void log(String msg) {
383 | }
384 |
385 | @SuppressWarnings("WeakerAccess")
386 | public static class TagSpan extends ForegroundColorSpan implements Parcelable {
387 | private String value;
388 | public boolean willRemove;
389 |
390 | public TagSpan(String value) {
391 | super(0xFF24cf5f);
392 | this.value = value;
393 | }
394 |
395 | public TagSpan(Parcel src) {
396 | super(src);
397 | value = src.readString();
398 | }
399 |
400 | @Override
401 | public int describeContents() {
402 | return 0;
403 | }
404 |
405 | public static final Creator