@Widget(widgetClass = QMUIEmptyView.class, iconRes = R.mipmap.icon_grid_empty_view)
public class QDEmptyViewFragment extends BaseFragment {
@BindView(R.id.topbar)
QMUITopBarLayout mTopBar;
@BindView(R.id.emptyView)
QMUIEmptyView mEmptyView;
private QDItemDescription mQDItemDescription;
@Override
protected View onCreateView() {
View root = LayoutInflater.from(getActivity()).inflate(R.layout.fragment_emptyview, null);
ButterKnife.bind(this, root);
mQDItemDescription = QDDataManager.getInstance().getDescription(this.getClass());
initTopBar();
return root;
}
private void initTopBar() {
mTopBar.addLeftBackImageButton().setOnClickListener(new View.OnClickListener() {
@Override
public void onClick(View v) {
popBackStack();
}
});
// 切换其他情况的按钮
mTopBar.addRightImageButton(R.mipmap.icon_topbar_overflow, R.id.topbar_right_change_button).setOnClickListener(new View.OnClickListener() {
@Override
public void onClick(View view) {
showBottomSheetList();
}
});
mTopBar.setTitle(mQDItemDescription.getName());
}
private void showBottomSheetList() {
new QMUIBottomSheet.BottomListSheetBuilder(getActivity())
.addItem(getResources().getString(R.string.emptyView_mode_title_double_text))
.addItem(getResources().getString(R.string.emptyView_mode_title_single_text))
.addItem(getResources().getString(R.string.emptyView_mode_title_loading))
.addItem(getResources().getString(R.string.emptyView_mode_title_single_text_and_button))
.addItem(getResources().getString(R.string.emptyView_mode_title_double_text_and_button))
.setOnSheetItemClickListener(new QMUIBottomSheet.BottomListSheetBuilder.OnSheetItemClickListener() {
@Override
public void onClick(QMUIBottomSheet dialog, View itemView, int position, String tag) {
dialog.dismiss();
switch (position) {
case 0:
mEmptyView.show(getResources().getString(R.string.emptyView_mode_desc_double), getResources().getString(R.string.emptyView_mode_desc_detail_double));
break;
case 1:
mEmptyView.show(getResources().getString(R.string.emptyView_mode_desc_single), null);
break;
case 2:
mEmptyView.show(true);
break;
case 3:
mEmptyView.show(false, getResources().getString(R.string.emptyView_mode_desc_fail_title), null, getResources().getString(R.string.emptyView_mode_desc_retry), null);
break;
case 4:
mEmptyView.show(false, getResources().getString(R.string.emptyView_mode_desc_fail_title), getResources().getString(R.string.emptyView_mode_desc_fail_desc), getResources().getString(R.string.emptyView_mode_desc_retry), null);
break;
default:
break;
}
}
})
.build()
布局文件定义了两个:
<com.qmuiteam.qmui.widget.QMUIEmptyView
android:id="@+id/emptyView"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:layout_marginTop="?attr/qmui_topbar_height"
android:background="@color/qmui_config_color_white"
app:qmui_title_text="@string/emptyView_mode_desc_double"
app:qmui_detail_text="@string/emptyView_mode_desc_detail_double"
android:fitsSystemWindows="true"/>
<com.qmuiteam.qmui.widget.QMUITopBarLayout
android:id="@+id/topbar"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:fitsSystemWindows="true"/>
在TopBar中有切换其他情况的按钮,设置点击事件,调用showBottomSheetList() 方法,在底部出现一个SheetList。增加Item,并且添加点击事件。
QMUIEmptyView.java
public class QMUIEmptyView extends FrameLayout {
private QMUILoadingView mLoadingView;
private TextView mTitleTextView;
private TextView mDetailTextView;
protected Button mButton;
public QMUIEmptyView(Context context) {
this(context,null);
}
public QMUIEmptyView(Context context, AttributeSet attrs) {
this(context,attrs,0);
}
public QMUIEmptyView(Context context, AttributeSet attrs, int defStyle) {
super(context, attrs, defStyle);
init();
TypedArray arr = context.obtainStyledAttributes(attrs, R.styleable.QMUIEmptyView);
Boolean attrShowLoading = arr.getBoolean(R.styleable.QMUIEmptyView_qmui_show_loading, false);
String attrTitleText = arr.getString(R.styleable.QMUIEmptyView_qmui_title_text);
String attrDetailText = arr.getString(R.styleable.QMUIEmptyView_qmui_detail_text);
String attrBtnText = arr.getString(R.styleable.QMUIEmptyView_qmui_btn_text);
arr.recycle();
show(attrShowLoading,attrTitleText,attrDetailText,attrBtnText,null);
}
private void init() {
LayoutInflater.from(getContext()).inflate(R.layout.qmui_empty_view, this, true);
mLoadingView = (QMUILoadingView)findViewById(R.id.empty_view_loading);
mTitleTextView = (TextView)findViewById(R.id.empty_view_title);
mDetailTextView = (TextView)findViewById(R.id.empty_view_detail);
mButton = (Button)findViewById(R.id.empty_view_button);
}
/**
* 显示emptyView
* @param loading 是否要显示loading
* @param titleText 标题的文字,不需要则传null
* @param detailText 详情文字,不需要则传null
* @param buttonText 按钮的文字,不需要按钮则传null
* @param onButtonClickListener 按钮的onClick监听,不需要则传null
*/
public void show(boolean loading, String titleText, String detailText, String buttonText, OnClickListener onButtonClickListener) {
setLoadingShowing(loading);
setTitleText(titleText);
setDetailText(detailText);
setButton(buttonText, onButtonClickListener);
show();
}
/**
* 用于显示emptyView并且只显示loading的情况,此时title、detail、button都被隐藏
* @param loading 是否显示loading
*/
public void show(boolean loading) {
setLoadingShowing(loading);
setTitleText(null);
setDetailText(null);
setButton(null, null);
show();
}
/**
* 用于显示纯文本的简单调用方法,此时loading、button均被隐藏
* @param titleText 标题的文字,不需要则传null
* @param detailText 详情文字,不需要则传null
*/
public void show(String titleText, String detailText) {
setLoadingShowing(false);
setTitleText(titleText);
setDetailText(detailText);
setButton(null, null);
show();
}
/**
* 显示emptyView,不建议直接使用,建议调用带参数的show()方法,方便控制所有子View的显示/隐藏
*/
public void show() {
setVisibility(VISIBLE);
}
/**
* 隐藏emptyView
*/
public void hide() {
setVisibility(GONE);
setLoadingShowing(false);
setTitleText(null);
setDetailText(null);
setButton(null, null);
}
public boolean isShowing() {
return getVisibility() == VISIBLE;
}
public boolean isLoading() {
return mLoadingView.getVisibility() == VISIBLE;
}
public void setLoadingShowing(boolean show) {
mLoadingView.setVisibility(show ? VISIBLE : GONE);
}
public void setTitleText(String text) {
mTitleTextView.setText(text);
mTitleTextView.setVisibility(text != null ? VISIBLE : GONE);
}
public void setDetailText(String text) {
mDetailTextView.setText(text);
mDetailTextView.setVisibility(text != null ? VISIBLE : GONE);
}
public void setTitleColor(int color) {
mTitleTextView.setTextColor(color);
}
public void setDetailColor(int color) {
mDetailTextView.setTextColor(color);
}
public void setButton(String text, OnClickListener onClickListener) {
mButton.setText(text);
mButton.setVisibility(text != null ? VISIBLE : GONE);
mButton.setOnClickListener(onClickListener);
}
}
显示emptyView
* @param loading 是否要显示loading
* @param titleText 标题的文字,不需要则传null
* @param detailText 详情文字,不需要则传null
* @param buttonText 按钮的文字,不需要按钮则传null
* @param onButtonClickListener 按钮的onClick监听,不需要则传null
@Widget(group = Group.Other, name = "固定宽度,内容均分")
public class QDTabSegmentFixModeFragment extends BaseFragment {
@BindView(R.id.topbar)
QMUITopBarLayout mTopBar;
@BindView(R.id.tabSegment)
QMUITabSegment mTabSegment;
@BindView(R.id.contentViewPager)
ViewPager mContentViewPager;
private Map<ContentPage, View> mPageMap = new HashMap<>();
private ContentPage mDestPage = ContentPage.Item1;
private QDItemDescription mQDItemDescription;
private PagerAdapter mPagerAdapter = new PagerAdapter() {
@Override
public boolean isViewFromObject(View view, Object object) {
return view == object;
}
@Override
public int getCount() {
return ContentPage.SIZE;
}
@Override
public Object instantiateItem(final ViewGroup container, int position) {
ContentPage page = ContentPage.getPage(position);
View view = getPageView(page);
ViewGroup.LayoutParams params = new ViewGroup.LayoutParams(ViewGroup.LayoutParams.MATCH_PARENT, ViewGroup.LayoutParams.MATCH_PARENT);
container.addView(view, params);
return view;
}
@Override
public void destroyItem(ViewGroup container, int position, Object object) {
container.removeView((View) object);
}
};
@Override
protected View onCreateView() {
View rootView = LayoutInflater.from(getActivity()).inflate(R.layout.fragment_tab_viewpager_layout, null);
ButterKnife.bind(this, rootView);
mQDItemDescription = QDDataManager.getInstance().getDescription(this.getClass());
initTopBar();
initTabAndPager();
return rootView;
}
private void initTopBar() {
mTopBar.addLeftBackImageButton().setOnClickListener(new View.OnClickListener() {
@Override
public void onClick(View v) {
popBackStack();
}
});
mTopBar.setTitle(mQDItemDescription.getName());
mTopBar.addRightImageButton(R.mipmap.icon_topbar_overflow, R.id.topbar_right_change_button)
.setOnClickListener(new View.OnClickListener() {
@Override
public void onClick(View v) {
showBottomSheetList();
}
});
}
private void showBottomSheetList() {
new QMUIBottomSheet.BottomListSheetBuilder(getActivity())
.addItem(getResources().getString(R.string.tabSegment_mode_general))
.addItem(getResources().getString(R.string.tabSegment_mode_bottom_indicator))
.addItem(getResources().getString(R.string.tabSegment_mode_top_indicator))
.addItem(getResources().getString(R.string.tabSegment_mode_indicator_with_content))
.addItem(getResources().getString(R.string.tabSegment_mode_left_icon_and_auto_tint))
.addItem(getResources().getString(R.string.tabSegment_mode_sign_count))
.addItem(getResources().getString(R.string.tabSegment_mode_icon_change))
.addItem(getResources().getString(R.string.tabSegment_mode_muti_color))
.addItem(getResources().getString(R.string.tabSegment_mode_change_content_by_index))
.addItem(getResources().getString(R.string.tabSegment_mode_replace_tab_by_index))
.setOnSheetItemClickListener(new QMUIBottomSheet.BottomListSheetBuilder.OnSheetItemClickListener() {
@Override
public void onClick(QMUIBottomSheet dialog, View itemView, int position, String tag) {
dialog.dismiss();
switch (position) {
case 0:
mTabSegment.reset();
mTabSegment.setHasIndicator(false);
mTabSegment.addTab(new QMUITabSegment.Tab(getString(R.string.tabSegment_item_1_title)));
mTabSegment.addTab(new QMUITabSegment.Tab(getString(R.string.tabSegment_item_2_title)));
break;
case 1:
mTabSegment.reset();
mTabSegment.setHasIndicator(true);
mTabSegment.setIndicatorPosition(false);
mTabSegment.setIndicatorWidthAdjustContent(true);
mTabSegment.addTab(new QMUITabSegment.Tab(getString(R.string.tabSegment_item_1_title)));
mTabSegment.addTab(new QMUITabSegment.Tab(getString(R.string.tabSegment_item_2_title)));
break;
case 2:
mTabSegment.reset();
mTabSegment.setHasIndicator(true);
mTabSegment.setIndicatorPosition(true);
mTabSegment.setIndicatorWidthAdjustContent(true);
mTabSegment.addTab(new QMUITabSegment.Tab(getString(R.string.tabSegment_item_1_title)));
mTabSegment.addTab(new QMUITabSegment.Tab(getString(R.string.tabSegment_item_2_title)));
break;
case 3:
mTabSegment.reset();
mTabSegment.setHasIndicator(true);
mTabSegment.setIndicatorPosition(false);
mTabSegment.setIndicatorWidthAdjustContent(false);
mTabSegment.addTab(new QMUITabSegment.Tab(getString(R.string.tabSegment_item_1_title)));
mTabSegment.addTab(new QMUITabSegment.Tab(getString(R.string.tabSegment_item_2_title)));
break;
case 4:
mTabSegment.reset();
mTabSegment.setHasIndicator(false);
QMUITabSegment.Tab component = new QMUITabSegment.Tab(
ContextCompat.getDrawable(getContext(), R.mipmap.icon_tabbar_component),
null,
"Components", true
);
QMUITabSegment.Tab util = new QMUITabSegment.Tab(
ContextCompat.getDrawable(getContext(), R.mipmap.icon_tabbar_util),
null,
"Helper", true
);
mTabSegment.addTab(component);
mTabSegment.addTab(util);
break;
case 5:
QMUITabSegment.Tab tab = mTabSegment.getTab(0);
tab.setSignCountMargin(0, -QMUIDisplayHelper.dp2px(getContext(), 4));
tab.showSignCountView(getContext(), 1);
break;
case 6:
mTabSegment.reset();
mTabSegment.setHasIndicator(false);
QMUITabSegment.Tab component2 = new QMUITabSegment.Tab(
ContextCompat.getDrawable(getContext(), R.mipmap.icon_tabbar_component),
ContextCompat.getDrawable(getContext(), R.mipmap.icon_tabbar_component_selected),
"Components", false
);
QMUITabSegment.Tab util2 = new QMUITabSegment.Tab(
ContextCompat.getDrawable(getContext(), R.mipmap.icon_tabbar_util),
ContextCompat.getDrawable(getContext(), R.mipmap.icon_tabbar_util_selected),
"Helper", false
);
mTabSegment.addTab(component2);
mTabSegment.addTab(util2);
break;
case 7:
mTabSegment.reset();
mTabSegment.setHasIndicator(true);
mTabSegment.setIndicatorWidthAdjustContent(true);
mTabSegment.setIndicatorPosition(false);
QMUITabSegment.Tab component3 = new QMUITabSegment.Tab(
ContextCompat.getDrawable(getContext(), R.mipmap.icon_tabbar_component),
null,
"Components", true
);
component3.setTextColor(QMUIResHelper.getAttrColor(getContext(), R.attr.qmui_config_color_blue),
QMUIResHelper.getAttrColor(getContext(), R.attr.qmui_config_color_red));
QMUITabSegment.Tab util3 = new QMUITabSegment.Tab(
ContextCompat.getDrawable(getContext(), R.mipmap.icon_tabbar_util),
null,
"Helper", true
);
util3.setTextColor(QMUIResHelper.getAttrColor(getContext(), R.attr.qmui_config_color_gray_1),
QMUIResHelper.getAttrColor(getContext(), R.attr.qmui_config_color_red));
mTabSegment.addTab(component3);
mTabSegment.addTab(util3);
break;
case 8:
mTabSegment.updateTabText(0, "动态更新文案");
break;
case 9:
QMUITabSegment.Tab component4 = new QMUITabSegment.Tab(
ContextCompat.getDrawable(getContext(), R.mipmap.icon_tabbar_component),
null,
"动态更新", true
);
mTabSegment.replaceTab(0, component4);
break;
default:
break;
}
mTabSegment.notifyDataChanged();
}
})
.build()
.show();
}
private void initTabAndPager() {
mContentViewPager.setAdapter(mPagerAdapter);
mContentViewPager.setCurrentItem(mDestPage.getPosition(), false);
mTabSegment.addTab(new QMUITabSegment.Tab(getString(R.string.tabSegment_item_1_title)));
mTabSegment.addTab(new QMUITabSegment.Tab(getString(R.string.tabSegment_item_2_title)));
mTabSegment.setupWithViewPager(mContentViewPager, false);
mTabSegment.setMode(QMUITabSegment.MODE_FIXED);
mTabSegment.addOnTabSelectedListener(new QMUITabSegment.OnTabSelectedListener() {
@Override
public void onTabSelected(int index) {
mTabSegment.hideSignCountView(index);
}
@Override
public void onTabUnselected(int index) {
}
@Override
public void onTabReselected(int index) {
mTabSegment.hideSignCountView(index);
}
@Override
public void onDoubleTap(int index) {
}
});
}
private View getPageView(ContentPage page) {
View view = mPageMap.get(page);
if (view == null) {
TextView textView = new TextView(getContext());
textView.setGravity(Gravity.CENTER);
textView.setTextSize(TypedValue.COMPLEX_UNIT_SP, 20);
textView.setTextColor(ContextCompat.getColor(getContext(), R.color.app_color_description));
if (page == ContentPage.Item1) {
textView.setText(R.string.tabSegment_item_1_content);
} else if (page == ContentPage.Item2) {
textView.setText(R.string.tabSegment_item_2_content);
}
view = textView;
mPageMap.put(page, view);
}
return view;
}
public enum ContentPage {
Item1(0),
Item2(1);
public static final int SIZE = 2;
private final int position;
ContentPage(int pos) {
position = pos;
}
public static ContentPage getPage(int position) {
switch (position) {
case 0:
return Item1;
case 1:
return Item2;
default:
return Item1;
}
}
public int getPosition() {
return position;
}
}
}
QDtabSegmentScrollableModeFragment
@Widget(group = Group.Other, name = "内容自适应,超过父容器则滚动")
public class QDTabSegmentScrollableModeFragment extends BaseFragment {
@SuppressWarnings("FieldCanBeLocal") private final int TAB_COUNT = 10;
@BindView(R.id.topbar) QMUITopBarLayout mTopBar;
@BindView(R.id.tabSegment) QMUITabSegment mTabSegment;
@BindView(R.id.contentViewPager) ViewPager mContentViewPager;
private Map<ContentPage, View> mPageMap = new HashMap<>();
private ContentPage mDestPage = ContentPage.Item1;
private QDItemDescription mQDItemDescription;
private int mCurrentItemCount = TAB_COUNT;
private PagerAdapter mPagerAdapter = new PagerAdapter() {
@Override
public boolean isViewFromObject(View view, Object object) {
return view == object;
}
@Override
public int getCount() {
return mCurrentItemCount;
}
@Override
public Object instantiateItem(final ViewGroup container, int position) {
ContentPage page = ContentPage.getPage(position);
View view = getPageView(page);
view.setTag(page);
ViewGroup.LayoutParams params = new ViewGroup.LayoutParams(ViewGroup.LayoutParams.MATCH_PARENT, ViewGroup.LayoutParams.MATCH_PARENT);
container.addView(view, params);
return view;
}
@Override
public void destroyItem(ViewGroup container, int position, Object object) {
container.removeView((View) object);
}
@Override
public int getItemPosition(@NonNull Object object) {
View view = (View) object;
Object page = view.getTag();
if (page instanceof ContentPage) {
int pos = ((ContentPage) page).getPosition();
if (pos >= mCurrentItemCount) {
return POSITION_NONE;
}
return POSITION_UNCHANGED;
}
return POSITION_NONE;
}
};
@Override
protected View onCreateView() {
View rootView = LayoutInflater.from(getActivity()).inflate(R.layout.fragment_tab_viewpager_layout, null);
ButterKnife.bind(this, rootView);
mQDItemDescription = QDDataManager.getInstance().getDescription(this.getClass());
initTopBar();
initTabAndPager();
return rootView;
}
private void initTopBar() {
mTopBar.addLeftBackImageButton().setOnClickListener(new View.OnClickListener() {
@Override
public void onClick(View v) {
popBackStack();
}
});
mTopBar.setTitle(mQDItemDescription.getName());
mTopBar.addRightTextButton("reduce tab", QMUIViewHelper.generateViewId())
.setOnClickListener(new View.OnClickListener() {
@Override
public void onClick(View v) {
reduceTabCount();
}
});
}
private void initTabAndPager() {
mContentViewPager.setAdapter(mPagerAdapter);
mContentViewPager.setCurrentItem(mDestPage.getPosition(), false);
for (int i = 0; i < mCurrentItemCount; i++) {
mTabSegment.addTab(new QMUITabSegment.Tab("Item " + (i + 1)));
}
int space = QMUIDisplayHelper.dp2px(getContext(), 16);
mTabSegment.setHasIndicator(true);
mTabSegment.setMode(QMUITabSegment.MODE_SCROLLABLE);
mTabSegment.setItemSpaceInScrollMode(space);
mTabSegment.setupWithViewPager(mContentViewPager, false);
mTabSegment.setPadding(space, 0, space, 0);
mTabSegment.addOnTabSelectedListener(new QMUITabSegment.OnTabSelectedListener() {
@Override
public void onTabSelected(int index) {
Toast.makeText(getContext(), "select index " + index, Toast.LENGTH_SHORT).show();
}
@Override
public void onTabUnselected(int index) {
Toast.makeText(getContext(), "unSelect index " + index, Toast.LENGTH_SHORT).show();
}
@Override
public void onTabReselected(int index) {
Toast.makeText(getContext(), "reSelect index " + index, Toast.LENGTH_SHORT).show();
}
@Override
public void onDoubleTap(int index) {
Toast.makeText(getContext(), "double tap index " + index, Toast.LENGTH_SHORT).show();
}
});
}
private void reduceTabCount() {
if (mCurrentItemCount <= 1) {
Toast.makeText(getContext(), "Only the last one, don't reduce it anymore!!!", Toast.LENGTH_SHORT).show();
return;
}
mCurrentItemCount--;
mPagerAdapter.notifyDataSetChanged();
mTabSegment.reset();
for (int i = 0; i < mCurrentItemCount; i++) {
mTabSegment.addTab(new QMUITabSegment.Tab("Item " + (i + 1)));
}
mTabSegment.notifyDataChanged();
}
private View getPageView(ContentPage page) {
View view = mPageMap.get(page);
if (view == null) {
TextView textView = new TextView(getContext());
textView.setGravity(Gravity.CENTER);
textView.setTextSize(TypedValue.COMPLEX_UNIT_SP, 20);
textView.setTextColor(ContextCompat.getColor(getContext(), R.color.app_color_description));
textView.setText("这是第 " + (page.getPosition() + 1) + " 个 Item 的内容区");
view = textView;
mPageMap.put(page, view);
}
return view;
}
public enum ContentPage {
Item1(0),
Item2(1),
Item3(2),
Item4(3),
Item5(4),
Item6(5),
Item7(6),
Item8(7),
Item9(8),
Item10(9);
private final int position;
ContentPage(int pos) {
position = pos;
}
public static ContentPage getPage(int position) {
switch (position) {
case 0:
return Item1;
case 1:
return Item2;
case 2:
return Item3;
case 3:
return Item4;
case 4:
return Item5;
case 5:
return Item6;
case 6:
return Item7;
case 7:
return Item8;
case 8:
return Item9;
case 9:
return Item10;
default:
return Item1;
}
}
public int getPosition() {
return position;
}
}
}
QDVerticalTextViewFragment.java
@Widget(widgetClass = QMUIVerticalTextView.class, iconRes = R.mipmap.icon_grid_vertical_text_view)
public class QDVerticalTextViewFragment extends BaseFragment {
@BindView(R.id.topbar)
QMUITopBarLayout mTopBar;
@BindView(R.id.verticalTextView)
QMUIVerticalTextView mVerticalTextView;
@BindView(R.id.verticalTextView_editText)
EditText mEditText;
@Override
protected View onCreateView() {
View rootView = LayoutInflater.from(getActivity()).inflate(R.layout.fragment_verticaltextview, null);
ButterKnife.bind(this, rootView);
initTopBar();
initVerticalTextView();
return rootView;
}
private void initTopBar() {
mTopBar.addLeftBackImageButton().setOnClickListener(new View.OnClickListener() {
@Override
public void onClick(View v) {
popBackStack();
}
});
QDItemDescription qdItemDescription = QDDataManager.getInstance().getDescription(this.getClass());
mTopBar.setTitle(qdItemDescription.getName());
}
private void initVerticalTextView() {
final String defaultText = String.format("%s 实现对文字的垂直排版。并且对非 CJK (中文、日文、韩文)字符做90度旋转排版。可以在下方的输入框中输入文字,体验不同文字垂直排版的效果。",
QMUIVerticalTextView.class.getSimpleName());
mVerticalTextView.setText(defaultText);
mEditText.addTextChangedListener(new TextWatcher() {
@Override
public void beforeTextChanged(CharSequence s, int start, int count, int after) {
}
@Override
public void onTextChanged(CharSequence s, int start, int before, int count) {
}
@Override
public void afterTextChanged(Editable s) {
mVerticalTextView.setText(QMUILangHelper.isNullOrEmpty(s) ? defaultText : s);
}
});
}
@Override
protected void popBackStack() {
super.popBackStack();
QMUIKeyboardHelper.hideKeyboard(mEditText);
}
}
QMUIVertucalTextView.java
public class QMUIVerticalTextView extends TextView {
/**
* 是否将文字显示成竖排
*/
private boolean mIsVerticalMode = true;
private int mLineCount; // 行数
private float[] mLineWidths; // 下标: 行号; 数组内容: 该行的宽度(由该行最宽的字符决定)
private int[] mLineBreakIndex; // 下标: 行号; 数组内容: 该行最后一个字符的下标
public QMUIVerticalTextView(Context context) {
super(context);
init();
}
public QMUIVerticalTextView(Context context, AttributeSet attrs) {
super(context, attrs);
init();
}
public QMUIVerticalTextView(Context context, AttributeSet attrs, int defStyleAttr) {
super(context, attrs, defStyleAttr);
init();
}
private void init() {
}
@SuppressLint("DrawAllocation")
@TargetApi(Build.VERSION_CODES.JELLY_BEAN)
@Override
protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
super.onMeasure(widthMeasureSpec, heightMeasureSpec);
if (mIsVerticalMode) {
int widthMode = MeasureSpec.getMode(widthMeasureSpec);
int heightMode = MeasureSpec.getMode(heightMeasureSpec);
int widthSize = MeasureSpec.getSize(widthMeasureSpec);
int heightSize = MeasureSpec.getSize(heightMeasureSpec);
float width = getPaddingLeft() + getPaddingRight();
float height = getPaddingTop() + getPaddingBottom();
char[] chars = getText().toString().toCharArray();
final Paint paint = getPaint();
final Paint.FontMetricsInt fontMetricsInt = paint.getFontMetricsInt();
final int lineMaxBottom = (heightMode == MeasureSpec.UNSPECIFIED ? Integer.MAX_VALUE : heightSize)
- getPaddingBottom();
float currentLineHeight = getPaddingTop();
float lineMaxHeight = currentLineHeight;
int lineIndex = 0;
mLineCount = 0;
mLineWidths = new float[chars.length + 1]; // 加1是为了处理高度不够放下一个字的情况,needBreakLine会一直为true直到最后一个字
mLineBreakIndex = new int[chars.length + 1];
// 从右向左,从上向下布局
for (int i = 0; i < chars.length; i++) {
final char c = chars[i];
// rotate
// boolean needRotate = !Caches.isCJK(c);
boolean needRotate = !isCJKCharacter(c);
// char height
float charHeight;
float charWidth;
if (needRotate) {
charWidth = fontMetricsInt.descent - fontMetricsInt.ascent;
charHeight = paint.measureText(chars, i, 1);
} else {
charWidth = paint.measureText(chars, i, 1);
charHeight = fontMetricsInt.descent - fontMetricsInt.ascent;
}
// is need break line
boolean needBreakLine = currentLineHeight + charHeight > lineMaxBottom
&& i > 0; // i > 0 是因为即使在第一列高度不够,也不用换下一列
if (needBreakLine) {
// break line
if (lineMaxHeight < currentLineHeight) {
lineMaxHeight = currentLineHeight;
}
mLineBreakIndex[lineIndex] = i - 1;
width += mLineWidths[lineIndex];
lineIndex++;
// reset
currentLineHeight = charHeight;
} else {
currentLineHeight += charHeight;
if (lineMaxHeight < currentLineHeight) {
lineMaxHeight = currentLineHeight;
}
}
if (mLineWidths[lineIndex] < charWidth) {
mLineWidths[lineIndex] = charWidth;
}
// last column width
if (i == chars.length - 1) {
width += mLineWidths[lineIndex];
height = lineMaxHeight + getPaddingBottom();
}
}
if (chars.length > 0) {
mLineCount = lineIndex + 1;
mLineBreakIndex[lineIndex] = chars.length - 1;
}
// 计算 lineSpacing
if (mLineCount > 1) {
int lineSpacingCount = mLineCount - 1;
for (int i = 0; i < lineSpacingCount; i++) {
width += mLineWidths[i] * (getLineSpacingMultiplier() - 1) + getLineSpacingExtra();
}
}
if (heightMode == MeasureSpec.EXACTLY) {
height = heightSize;
} else if (heightMode == MeasureSpec.AT_MOST) {
height = Math.min(height, heightSize);
}
if (widthMode == MeasureSpec.EXACTLY) {
width = widthSize;
} else if (widthMode == MeasureSpec.AT_MOST) {
width = Math.min(width, widthSize);
}
setMeasuredDimension((int) width, (int) height);
}
}
@TargetApi(Build.VERSION_CODES.JELLY_BEAN)
@Override
protected void onDraw(Canvas canvas) {
if (!mIsVerticalMode) {
super.onDraw(canvas);
} else {
if (mLineCount == 0) {
return;
}
final TextPaint paint = getPaint();
paint.setColor(getCurrentTextColor());
paint.drawableState = getDrawableState();
final Paint.FontMetricsInt fontMetricsInt = paint.getFontMetricsInt();
final char[] chars = getText().toString().toCharArray();
canvas.save();
int curLine = 0;
float curLineX = getWidth() - getPaddingRight() - mLineWidths[curLine];
float curX = curLineX;
float curY = getPaddingTop();
for (int i = 0; i < chars.length; i++) {
final char c = chars[i];
// boolean needRotate = !Caches.isCJK(c);
boolean needRotate = !isCJKCharacter(c);
final int saveCount = canvas.save();
if (needRotate) {
canvas.rotate(90, curX, curY);
}
// draw
float textX = curX;
float textBaseline = needRotate ?
curY - (mLineWidths[curLine] - (fontMetricsInt.bottom - fontMetricsInt.top)) / 2 - fontMetricsInt.descent :
curY - fontMetricsInt.ascent;
canvas.drawText(chars, i, 1, textX, textBaseline, paint);
canvas.restoreToCount(saveCount);
// if break line
boolean hasNextChar = i + 1 < chars.length;
if (hasNextChar) {
// boolean breakLine = needBreakLine(i, mLineCharsCount, curLine);
boolean nextCharBreakLine = i + 1 > mLineBreakIndex[curLine];
if (nextCharBreakLine && curLine + 1 < mLineWidths.length) {
// new line
curLine++;
curLineX -= (mLineWidths[curLine] * getLineSpacingMultiplier() + getLineSpacingExtra());
curX = curLineX;
curY = getPaddingTop();
} else {
// move to next char
if (needRotate) {
curY += paint.measureText(chars, i, 1);
} else {
curY += fontMetricsInt.descent - fontMetricsInt.ascent;
}
}
}
}
canvas.restore();
}
}
// This method is copied from moai.ik.helper.CharacterHelper.isCJKCharacter(char input)
private static boolean isCJKCharacter(char input) {
Character.UnicodeBlock ub = Character.UnicodeBlock.of(input);
//noinspection RedundantIfStatement
if (ub == Character.UnicodeBlock.CJK_UNIFIED_IDEOGRAPHS
|| ub == Character.UnicodeBlock.CJK_COMPATIBILITY_IDEOGRAPHS
|| ub == Character.UnicodeBlock.CJK_UNIFIED_IDEOGRAPHS_EXTENSION_A
//全角数字字符和日韩字符
|| ub == Character.UnicodeBlock.HALFWIDTH_AND_FULLWIDTH_FORMS
//韩文字符集
|| ub == Character.UnicodeBlock.HANGUL_SYLLABLES
|| ub == Character.UnicodeBlock.HANGUL_JAMO
|| ub == Character.UnicodeBlock.HANGUL_COMPATIBILITY_JAMO
//日文字符集
|| ub == Character.UnicodeBlock.HIRAGANA //平假名
|| ub == Character.UnicodeBlock.KATAKANA //片假名
|| ub == Character.UnicodeBlock.KATAKANA_PHONETIC_EXTENSIONS
) {
return true;
} else {
return false;
}
//其他的CJK标点符号,可以不做处理
//|| ub == Character.UnicodeBlock.CJK_SYMBOLS_AND_PUNCTUATION
//|| ub == Character.UnicodeBlock.GENERAL_PUNCTUATION
}
public void setVerticalMode(boolean verticalMode) {
mIsVerticalMode = verticalMode;
requestLayout();
}
public boolean isVerticalMode() {
return mIsVerticalMode;
}}
// 场景一
mSystemTv1.setMovementMethod(LinkMovementMethod.getInstance());
mSystemTv1.setText(generateSp(getResources().getString(R.string.system_behavior_1)));
mSpanTouchFixTextView1.setMovementMethodDefault();
mSpanTouchFixTextView1.setText(generateSp(getResources().getString(R.string.span_touch_fix_1)));
// 场景二
mSystemTv2.setMovementMethod(LinkMovementMethod.getInstance());
mSystemTv2.setText(generateSp(getResources().getString(R.string.system_behavior_2)));
mSpanTouchFixTextView2.setMovementMethodDefault();
mSpanTouchFixTextView2.setNeedForceEventToParent(true);
mSpanTouchFixTextView2.setText(generateSp(getResources().getString(R.string.span_touch_fix_2)));
return view;
}
private SpannableString generateSp(String text) {
String highlight1 = "@qmui";
String highlight2 = "#qmui#";
SpannableString sp = new SpannableString(text);
int start = 0, end;
int index;
while ((index = text.indexOf(highlight1, start)) > -1) {
end = index + highlight1.length();
sp.setSpan(new QMUITouchableSpan(highlightTextNormalColor, highlightTextPressedColor,
highlightBgNormalColor, highlightBgPressedColor) {
@Override
public void onSpanClick(View widget) {
Toast.makeText(getContext(), "click @qmui", Toast.LENGTH_SHORT).show();
}
}, index, end, Spanned.SPAN_INCLUSIVE_EXCLUSIVE);
start = end;
}
start = 0;
while ((index = text.indexOf(highlight2, start)) > -1) {
end = index + highlight2.length();
sp.setSpan(new QMUITouchableSpan(highlightTextNormalColor, highlightTextPressedColor,
highlightBgNormalColor, highlightBgPressedColor) {
@Override
public void onSpanClick(View widget) {
Toast.makeText(getContext(), "click #qmui#", Toast.LENGTH_SHORT).show();
}
}, index, end, Spanned.SPAN_INCLUSIVE_EXCLUSIVE);
start = end;
}
return sp;
}
private void initTopBar() {
mTopBar.setTitle(QDDataManager.getInstance().getDescription(this.getClass()).getName());
mTopBar.addLeftBackImageButton().setOnClickListener(new View.OnClickListener() {
@Override
public void onClick(View v) {
popBackStack();
}
});
}
}
public enum QMUIDirection {
LEFT_TO_RIGHT,
TOP_TO_BOTTOM,
RIGHT_TO_LEFT,
BOTTOM_TO_TOP
}
应用QMUITouchableSpan类
下拉出现圆形箭头旋转动画,三种方式
1:下拉到一定距离之后又Loading不动
2:一直跟随下拉
3.:保持在下拉区中间
public class QMUIPullRefreshLayout extends ViewGroup implements NestedScrollingParent {
private static final String TAG = "QMUIPullRefreshLayout";
private static final int INVALID_POINTER = -1;
private static final int FLAG_NEED_SCROLL_TO_INIT_POSITION = 1;
private static final int FLAG_NEED_SCROLL_TO_REFRESH_POSITION = 1 << 1;
private static final int FLAG_NEED_DO_REFRESH = 1 << 2;
private static final int FLAG_NEED_DELIVER_VELOCITY = 1 << 3;
private final NestedScrollingParentHelper mNestedScrollingParentHelper;
boolean mIsRefreshing = false;
private View mTargetView;
private IRefreshView mIRefreshView;
private View mRefreshView;
private int mRefreshZIndex = -1;
private int mSystemTouchSlop;
private int mTouchSlop;
private OnPullListener mListener;
private OnChildScrollUpCallback mChildScrollUpCallback;
/**
* RefreshView的初始offset
*/
private int mRefreshInitOffset;
/**
* 刷新时RefreshView的offset
*/
private int mRefreshEndOffset;
/**
* RefreshView当前offset
*/
private int mRefreshCurrentOffset;
/**
* 是否自动根据RefreshView的高度计算RefreshView的初始位置
*/
private boolean mAutoCalculateRefreshInitOffset = true;
/**
* 是否自动根据TargetView在刷新时的位置计算RefreshView的结束位置
*/
private boolean mAutoCalculateRefreshEndOffset = true;
/**
* 自动让TargetView的刷新位置与RefreshView高度相等
*/
private boolean mEqualTargetRefreshOffsetToRefreshViewHeight = false;
/**
* 当拖拽超过超过mAutoScrollToRefreshMinOffset时,自动滚动到刷新位置并触发刷新
* mAutoScrollToRefreshMinOffset == - 1表示要mAutoScrollToRefreshMinOffset>=mTargetRefreshOffset
*/
private int mAutoScrollToRefreshMinOffset = -1;
/**
* TargetView(ListView或者ScrollView等)的初始位置
*/
private int mTargetInitOffset;
/**
* 下拉时 TargetView(ListView 或者 ScrollView 等)当前的位置。
*/
private int mTargetCurrentOffset;
/**
* 刷新时TargetView(ListView或者ScrollView等)的位置
*/
private int mTargetRefreshOffset;
private boolean mEnableOverPull = true;
private boolean mNestedScrollInProgress;
private int mActivePointerId = INVALID_POINTER;
private boolean mIsDragging;
private float mInitialDownY;
private float mInitialDownX;
@SuppressWarnings("FieldCanBeLocal") private float mInitialMotionY;
private float mLastMotionY;
@SuppressWarnings("FieldCanBeLocal") private float mDragRate = 0.65f;
private RefreshOffsetCalculator mRefreshOffsetCalculator;
private VelocityTracker mVelocityTracker;
private float mMaxVelocity;
private float mMiniVelocity;
private Scroller mScroller;
private int mScrollFlag = 0;
private boolean mNestScrollDurationRefreshing = false;
public QMUIPullRefreshLayout(Context context) {
this(context, null);
}
public QMUIPullRefreshLayout(Context context, AttributeSet attrs) {
this(context, attrs, R.attr.QMUIPullRefreshLayoutStyle);
}
public QMUIPullRefreshLayout(Context context, AttributeSet attrs, int defStyleAttr) {
super(context, attrs, defStyleAttr);
setWillNotDraw(false);
final ViewConfiguration vc = ViewConfiguration.get(context);
mMaxVelocity = vc.getScaledMaximumFlingVelocity();
mMiniVelocity = vc.getScaledMinimumFlingVelocity();
mSystemTouchSlop = vc.getScaledTouchSlop();
mTouchSlop = QMUIDisplayHelper.px2dp(context, mSystemTouchSlop); //系统的值是8dp,如何配置?
mScroller = new Scroller(getContext());
mScroller.setFriction(getScrollerFriction());
addRefreshView();
ViewCompat.setChildrenDrawingOrderEnabled(this, true);
mNestedScrollingParentHelper = new NestedScrollingParentHelper(this);
TypedArray array = context.obtainStyledAttributes(attrs,
R.styleable.QMUIPullRefreshLayout, defStyleAttr, 0);
try {
mRefreshInitOffset = array.getDimensionPixelSize(
R.styleable.QMUIPullRefreshLayout_qmui_refresh_init_offset, Integer.MIN_VALUE);
mRefreshEndOffset = array.getDimensionPixelSize(
R.styleable.QMUIPullRefreshLayout_qmui_refresh_end_offset, Integer.MIN_VALUE);
mTargetInitOffset = array.getDimensionPixelSize(
R.styleable.QMUIPullRefreshLayout_qmui_target_init_offset, 0);
mTargetRefreshOffset = array.getDimensionPixelSize(
R.styleable.QMUIPullRefreshLayout_qmui_target_refresh_offset,
QMUIDisplayHelper.dp2px(getContext(), 72));
mAutoCalculateRefreshInitOffset = mRefreshInitOffset == Integer.MIN_VALUE ||
array.getBoolean(R.styleable.QMUIPullRefreshLayout_qmui_auto_calculate_refresh_init_offset, false);
mAutoCalculateRefreshEndOffset = mRefreshEndOffset == Integer.MIN_VALUE ||
array.getBoolean(R.styleable.QMUIPullRefreshLayout_qmui_auto_calculate_refresh_end_offset, false);
mEqualTargetRefreshOffsetToRefreshViewHeight = array.getBoolean(R.styleable.QMUIPullRefreshLayout_qmui_equal_target_refresh_offset_to_refresh_view_height, false);
} finally {
array.recycle();
}
mRefreshCurrentOffset = mRefreshInitOffset;
mTargetCurrentOffset = mTargetInitOffset;
}
public static boolean defaultCanScrollUp(View view) {
if (view == null) {
return false;
}
if (android.os.Build.VERSION.SDK_INT < 14) {
if (view instanceof AbsListView) {
final AbsListView absListView = (AbsListView) view;
return absListView.getChildCount() > 0
&& (absListView.getFirstVisiblePosition() > 0 || absListView.getChildAt(0)
.getTop() < absListView.getPaddingTop());
} else {
return ViewCompat.canScrollVertically(view, -1) || view.getScrollY() > 0;
}
} else {
return ViewCompat.canScrollVertically(view, -1);
}
}
public void setOnPullListener(OnPullListener listener) {
mListener = listener;
}
public void setChildScrollUpCallback(OnChildScrollUpCallback childScrollUpCallback) {
mChildScrollUpCallback = childScrollUpCallback;
}
protected float getScrollerFriction() {
return ViewConfiguration.getScrollFriction();
}
public void setAutoScrollToRefreshMinOffset(int autoScrollToRefreshMinOffset) {
mAutoScrollToRefreshMinOffset = autoScrollToRefreshMinOffset;
}
/**
* 覆盖该方法以实现自己的 RefreshView。
*
* @return 自定义的 RefreshView, 注意该 View 必须实现 {@link IRefreshView} 接口
*/
protected View createRefreshView() {
return new RefreshView(getContext());
}
private void addRefreshView() {
if (mRefreshView == null) {
mRefreshView = createRefreshView();
}
if (!(mRefreshView instanceof IRefreshView)) {
throw new RuntimeException("refreshView must be a instance of IRefreshView");
}
mIRefreshView = (IRefreshView) mRefreshView;
if (mRefreshView.getLayoutParams() == null) {
mRefreshView.setLayoutParams(new LayoutParams(
LayoutParams.WRAP_CONTENT, LayoutParams.WRAP_CONTENT));
}
addView(mRefreshView);
}
/**
* 设置在下拉过程中 RefreshView 的偏移量
*/
public void setRefreshOffsetCalculator(RefreshOffsetCalculator refreshOffsetCalculator) {
mRefreshOffsetCalculator = refreshOffsetCalculator;
}
@Override
protected int getChildDrawingOrder(int childCount, int i) {
if (mRefreshZIndex < 0) {
return i;
}
// 最后才绘制mRefreshView
if (i == mRefreshZIndex) {
return childCount - 1;
}
if (i > mRefreshZIndex) {
return i - 1;
}
return i;
}
@Override
public void requestDisallowInterceptTouchEvent(boolean b) {
// if this is a List < L or another view that doesn't support nested
// scrolling, ignore this request so that the vertical scroll event
// isn't stolen
//noinspection StatementWithEmptyBody
if ((android.os.Build.VERSION.SDK_INT < 21 && mTargetView instanceof AbsListView)
|| (mTargetView != null && !ViewCompat.isNestedScrollingEnabled(mTargetView))) {
// Nope.
} else {
super.requestDisallowInterceptTouchEvent(b);
}
}
@Override
protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
super.onMeasure(widthMeasureSpec, heightMeasureSpec);
ensureTargetView();
if (mTargetView == null) {
Log.d(TAG, "onMeasure: mTargetView == null");
return;
}
int targetMeasureWidthSpec = MeasureSpec.makeMeasureSpec(
getMeasuredWidth() - getPaddingLeft() - getPaddingRight(), MeasureSpec.EXACTLY);
int targetMeasureHeightSpec = MeasureSpec.makeMeasureSpec(
getMeasuredHeight() - getPaddingTop() - getPaddingBottom(), MeasureSpec.EXACTLY);
mTargetView.measure(targetMeasureWidthSpec, targetMeasureHeightSpec);
measureChild(mRefreshView, widthMeasureSpec, heightMeasureSpec);
mRefreshZIndex = -1;
for (int i = 0; i < getChildCount(); i++) {
if (getChildAt(i) == mRefreshView) {
mRefreshZIndex = i;
break;
}
}
int refreshViewHeight = mRefreshView.getMeasuredHeight();
if (mAutoCalculateRefreshInitOffset) {
if (mRefreshInitOffset != -refreshViewHeight) {
mRefreshInitOffset = -refreshViewHeight;
mRefreshCurrentOffset = mRefreshInitOffset;
}
}
if (mEqualTargetRefreshOffsetToRefreshViewHeight) {
mTargetRefreshOffset = refreshViewHeight;
}
if (mAutoCalculateRefreshEndOffset) {
mRefreshEndOffset = (mTargetRefreshOffset - refreshViewHeight) / 2;
}
}
@Override
protected void onLayout(boolean changed, int l, int t, int r, int b) {
final int width = getMeasuredWidth();
final int height = getMeasuredHeight();
if (getChildCount() == 0) {
return;
}
ensureTargetView();
if (mTargetView == null) {
Log.d(TAG, "onLayout: mTargetView == null");
return;
}
final int childLeft = getPaddingLeft();
final int childTop = getPaddingTop();
final int childWidth = width - getPaddingLeft() - getPaddingRight();
final int childHeight = height - getPaddingTop() - getPaddingBottom();
mTargetView.layout(childLeft, childTop + mTargetCurrentOffset,
childLeft + childWidth, childTop + childHeight + mTargetCurrentOffset);
int refreshViewWidth = mRefreshView.getMeasuredWidth();
int refreshViewHeight = mRefreshView.getMeasuredHeight();
mRefreshView.layout((width / 2 - refreshViewWidth / 2), mRefreshCurrentOffset,
(width / 2 + refreshViewWidth / 2), mRefreshCurrentOffset + refreshViewHeight);
}
@Override
public boolean onInterceptTouchEvent(MotionEvent ev) {
ensureTargetView();
final int action = ev.getAction();
int pointerIndex;
if (!isEnabled() || canChildScrollUp() || mNestedScrollInProgress) {
if (BuildConfig.DEBUG) {
Log.d(TAG, "fast end onIntercept: isEnabled = " + isEnabled() + "; canChildScrollUp = "
+ canChildScrollUp() + " ; mNestedScrollInProgress = " + mNestedScrollInProgress);
}
return false;
}
switch (action) {
case MotionEvent.ACTION_DOWN:
mIsDragging = false;
mActivePointerId = ev.getPointerId(0);
pointerIndex = ev.findPointerIndex(mActivePointerId);
if (pointerIndex < 0) {
return false;
}
mInitialDownX = ev.getX(pointerIndex);
mInitialDownY = ev.getY(pointerIndex);
break;
case MotionEvent.ACTION_MOVE:
pointerIndex = ev.findPointerIndex(mActivePointerId);
if (pointerIndex < 0) {
Log.e(TAG, "Got ACTION_MOVE event but have an invalid active pointer id.");
return false;
}
final float x = ev.getX(pointerIndex);
final float y = ev.getY(pointerIndex);
startDragging(x, y);
break;
case MotionEventCompat.ACTION_POINTER_UP:
onSecondaryPointerUp(ev);
break;
case MotionEvent.ACTION_UP:
case MotionEvent.ACTION_CANCEL:
mIsDragging = false;
mActivePointerId = INVALID_POINTER;
break;
}
return mIsDragging;
}
@Override
public boolean onTouchEvent(MotionEvent ev) {
final int action = ev.getAction();
int pointerIndex;
if (!isEnabled() || canChildScrollUp() || mNestedScrollInProgress) {
Log.d(TAG, "fast end onTouchEvent: isEnabled = " + isEnabled() + "; canChildScrollUp = "
+ canChildScrollUp() + " ; mNestedScrollInProgress = " + mNestedScrollInProgress);
return false;
}
acquireVelocityTracker(ev);
switch (action) {
case MotionEvent.ACTION_DOWN:
mIsDragging = false;
mScrollFlag = 0;
if (!mScroller.isFinished()) {
mScroller.abortAnimation();
}
mActivePointerId = ev.getPointerId(0);
break;
case MotionEvent.ACTION_MOVE: {
pointerIndex = ev.findPointerIndex(mActivePointerId);
if (pointerIndex < 0) {
Log.e(TAG, "onTouchEvent Got ACTION_MOVE event but have an invalid active pointer id.");
return false;
}
final float x = ev.getX(pointerIndex);
final float y = ev.getY(pointerIndex);
startDragging(x, y);
if (mIsDragging) {
float dy = (y - mLastMotionY) * mDragRate;
if (dy >= 0) {
moveTargetView(dy, true);
} else {
int move = moveTargetView(dy, true);
float delta = Math.abs(dy) - Math.abs(move);
if (delta > 0) {
// 重新dispatch一次down事件,使得列表可以继续滚动
ev.setAction(MotionEvent.ACTION_DOWN);
// 立刻dispatch一个大于mSystemTouchSlop的move事件,防止触发TargetView
float offsetLoc = mSystemTouchSlop + 1;
if (delta > offsetLoc) {
offsetLoc = delta;
}
ev.offsetLocation(0, offsetLoc);
super.dispatchTouchEvent(ev);
ev.setAction(action);
// 再dispatch一次move事件,消耗掉所有dy
ev.offsetLocation(0, -offsetLoc);
super.dispatchTouchEvent(ev);
}
}
mLastMotionY = y;
}
break;
}
case MotionEvent.ACTION_POINTER_DOWN: {
pointerIndex = MotionEventCompat.getActionIndex(ev);
if (pointerIndex < 0) {
Log.e(TAG, "Got ACTION_POINTER_DOWN event but have an invalid action index.");
return false;
}
mActivePointerId = ev.getPointerId(pointerIndex);
break;
}
case MotionEvent.ACTION_POINTER_UP:
onSecondaryPointerUp(ev);
break;
case MotionEvent.ACTION_UP: {
pointerIndex = ev.findPointerIndex(mActivePointerId);
if (pointerIndex < 0) {
Log.e(TAG, "Got ACTION_UP event but don't have an active pointer id.");
return false;
}
if (mIsDragging) {
mIsDragging = false;
mVelocityTracker.computeCurrentVelocity(1000, mMaxVelocity);
float vy = mVelocityTracker.getYVelocity(mActivePointerId);
if (Math.abs(vy) < mMiniVelocity) {
vy = 0;
}
finishPull((int) vy);
}
mActivePointerId = INVALID_POINTER;
releaseVelocityTracker();
return false;
}
case MotionEvent.ACTION_CANCEL:
releaseVelocityTracker();
return false;
}
return true;
}
private void ensureTargetView() {
if (mTargetView == null) {
for (int i = 0; i < getChildCount(); i++) {
View view = getChildAt(i);
if (!view.equals(mRefreshView)) {
onSureTargetView(view);
mTargetView = view;
break;
}
}
}
}
/**
* 确定TargetView, 提供机会给子类来做一些初始化的操作
*/
protected void onSureTargetView(View targetView) {
}
protected void onFinishPull(int vy, int refreshInitOffset, int refreshEndOffset, int refreshViewHeight,
int targetCurrentOffset, int targetInitOffset, int targetRefreshOffset) {
}
private void finishPull(int vy) {
info("finishPull: vy = " + vy + " ; mTargetCurrentOffset = " + mTargetCurrentOffset +
" ; mTargetRefreshOffset = " + mTargetRefreshOffset + " ; mTargetInitOffset = " + mTargetInitOffset +
" ; mScroller.isFinished() = " + mScroller.isFinished());
int miniVy = vy / 1000; // 向下拖拽时, 速度不能太大
onFinishPull(miniVy, mRefreshInitOffset, mRefreshEndOffset, mRefreshView.getHeight(),
mTargetCurrentOffset, mTargetInitOffset, mTargetRefreshOffset);
if (mTargetCurrentOffset >= mTargetRefreshOffset) {
if (miniVy > 0) {
mScrollFlag = FLAG_NEED_SCROLL_TO_REFRESH_POSITION | FLAG_NEED_DO_REFRESH;
mScroller.fling(0, mTargetCurrentOffset, 0, miniVy,
0, 0, mTargetInitOffset, Integer.MAX_VALUE);
invalidate();
} else if (miniVy < 0) {
mScroller.fling(0, mTargetCurrentOffset, 0, vy,
0, 0, Integer.MIN_VALUE, Integer.MAX_VALUE);
if (mScroller.getFinalY() < mTargetInitOffset) {
mScrollFlag = FLAG_NEED_DELIVER_VELOCITY;
} else if (mScroller.getFinalY() < mTargetRefreshOffset) {
int dy = mTargetInitOffset - mTargetCurrentOffset;
mScroller.startScroll(0, mTargetCurrentOffset, 0, dy);
} else if (mScroller.getFinalY() == mTargetRefreshOffset) {
mScrollFlag = FLAG_NEED_DO_REFRESH;
} else {
mScroller.startScroll(0, mTargetCurrentOffset, 0, mTargetRefreshOffset - mTargetCurrentOffset);
mScrollFlag = FLAG_NEED_DO_REFRESH;
}
invalidate();
} else {
if (mTargetCurrentOffset > mTargetRefreshOffset) {
mScroller.startScroll(0, mTargetCurrentOffset, 0, mTargetRefreshOffset - mTargetCurrentOffset);
}
mScrollFlag = FLAG_NEED_DO_REFRESH;
invalidate();
}
} else {
if (miniVy > 0) {
mScroller.fling(0, mTargetCurrentOffset, 0, miniVy, 0, 0, mTargetInitOffset, Integer.MAX_VALUE);
if (mScroller.getFinalY() > mTargetRefreshOffset) {
mScrollFlag = FLAG_NEED_SCROLL_TO_REFRESH_POSITION | FLAG_NEED_DO_REFRESH;
} else if (mAutoScrollToRefreshMinOffset >= 0 && mScroller.getFinalY() > mAutoScrollToRefreshMinOffset) {
mScroller.startScroll(0, mTargetCurrentOffset, 0, mTargetRefreshOffset - mTargetCurrentOffset);
mScrollFlag = FLAG_NEED_DO_REFRESH;
} else {
mScrollFlag = FLAG_NEED_SCROLL_TO_INIT_POSITION;
}
invalidate();
} else if (miniVy < 0) {
mScrollFlag = 0;
mScroller.fling(0, mTargetCurrentOffset, 0, vy, 0, 0, Integer.MIN_VALUE, Integer.MAX_VALUE);
if (mScroller.getFinalY() < mTargetInitOffset) {
mScrollFlag = FLAG_NEED_DELIVER_VELOCITY;
} else {
mScroller.startScroll(0, mTargetCurrentOffset, 0, mTargetInitOffset - mTargetCurrentOffset);
mScrollFlag = 0;
}
invalidate();
} else {
if (mTargetCurrentOffset == mTargetInitOffset) {
return;
}
if (mAutoScrollToRefreshMinOffset >= 0 && mTargetCurrentOffset >= mAutoScrollToRefreshMinOffset) {
mScroller.startScroll(0, mTargetCurrentOffset, 0, mTargetRefreshOffset - mTargetCurrentOffset);
mScrollFlag = FLAG_NEED_DO_REFRESH;
} else {
mScroller.startScroll(0, mTargetCurrentOffset, 0, mTargetInitOffset - mTargetCurrentOffset);
mScrollFlag = 0;
}
invalidate();
}
}
}
protected void onRefresh() {
if (mIsRefreshing) {
return;
}
mIsRefreshing = true;
mIRefreshView.doRefresh();
if (mListener != null) {
mListener.onRefresh();
}
}
public void finishRefresh() {
mIsRefreshing = false;
mIRefreshView.stop();
mScrollFlag = FLAG_NEED_SCROLL_TO_INIT_POSITION;
mScroller.forceFinished(true);
invalidate();
}
public void setEnableOverPull(boolean enableOverPull) {
mEnableOverPull = enableOverPull;
}
private void onSecondaryPointerUp(MotionEvent ev) {
final int pointerIndex = MotionEventCompat.getActionIndex(ev);
final int pointerId = ev.getPointerId(pointerIndex);
if (pointerId == mActivePointerId) {
// This was our active pointer going up. Choose a new
// active pointer and adjust accordingly.
final int newPointerIndex = pointerIndex == 0 ? 1 : 0;
mActivePointerId = ev.getPointerId(newPointerIndex);
}
}
public void reset() {
moveTargetViewTo(mTargetInitOffset, false);
mIRefreshView.stop();
mIsRefreshing = false;
mScroller.forceFinished(true);
mScrollFlag = 0;
}
protected void startDragging(float x, float y) {
final float dx = x - mInitialDownX;
final float dy = y - mInitialDownY;
boolean isYDrag = isYDrag(dx, dy);
if (isYDrag && (dy > mTouchSlop || (dy < -mTouchSlop && mTargetCurrentOffset > mTargetInitOffset)) && !mIsDragging) {
mInitialMotionY = mInitialDownY + mTouchSlop;
mLastMotionY = mInitialMotionY;
mIsDragging = true;
}
}
protected boolean isYDrag(float dx, float dy) {
return Math.abs(dy) > Math.abs(dx);
}
@Override
protected void onDetachedFromWindow() {
super.onDetachedFromWindow();
reset();
}
@Override
public void setEnabled(boolean enabled) {
super.setEnabled(enabled);
if (!enabled) {
reset();
invalidate();
}
}
public boolean canChildScrollUp() {
if (mChildScrollUpCallback != null) {
return mChildScrollUpCallback.canChildScrollUp(this, mTargetView);
}
return defaultCanScrollUp(mTargetView);
}
@Override
public boolean onStartNestedScroll(View child, View target, int nestedScrollAxes) {
info("onStartNestedScroll: nestedScrollAxes = " + nestedScrollAxes);
return isEnabled() && (nestedScrollAxes & ViewCompat.SCROLL_AXIS_VERTICAL) != 0;
}
@Override
public void onNestedScrollAccepted(View child, View target, int axes) {
info("onNestedScrollAccepted: axes = " + axes);
mScroller.abortAnimation();
mNestedScrollingParentHelper.onNestedScrollAccepted(child, target, axes);
mNestedScrollInProgress = true;
}
@Override
public void onNestedPreScroll(View target, int dx, int dy, int[] consumed) {
info("onNestedPreScroll: dx = " + dx + " ; dy = " + dy);
int parentCanConsume = mTargetCurrentOffset - mTargetInitOffset;
if (dy > 0 && parentCanConsume > 0) {
if (dy >= parentCanConsume) {
consumed[1] = parentCanConsume;
moveTargetViewTo(mTargetInitOffset, true);
} else {
consumed[1] = dy;
moveTargetView(-dy, true);
}
}
}
@Override
public void onNestedScroll(View target, int dxConsumed, int dyConsumed, int dxUnconsumed, int dyUnconsumed) {
info("onNestedScroll: dxConsumed = " + dxConsumed + " ; dyConsumed = " + dyConsumed +
" ; dxUnconsumed = " + dxUnconsumed + " ; dyUnconsumed = " + dyUnconsumed);
if (dyUnconsumed < 0 && !canChildScrollUp() && mScroller.isFinished() && mScrollFlag == 0) {
moveTargetView(-dyUnconsumed, true);
}
}
@Override
public int getNestedScrollAxes() {
return mNestedScrollingParentHelper.getNestedScrollAxes();
}
@Override
public void onStopNestedScroll(View child) {
info("onStopNestedScroll: mNestedScrollInProgress = " + mNestedScrollInProgress);
mNestedScrollingParentHelper.onStopNestedScroll(child);
if (mNestedScrollInProgress) {
mNestedScrollInProgress = false;
if (!mNestScrollDurationRefreshing) {
finishPull(0);
}
}
}
@Override
public boolean onNestedPreFling(View target, float velocityX, float velocityY) {
info("onNestedPreFling: mTargetCurrentOffset = " + mTargetCurrentOffset +
" ; velocityX = " + velocityX + " ; velocityY = " + velocityY);
if (mTargetCurrentOffset > mTargetInitOffset) {
mNestedScrollInProgress = false;
if (!mNestScrollDurationRefreshing) {
finishPull((int) -velocityY);
}
return true;
}
return false;
}
@Override
public boolean onNestedFling(View target, float velocityX, float velocityY, boolean consumed) {
try {
return super.onNestedFling(target, velocityX, velocityY, consumed);
} catch (Throwable e) {
// android 24及以上ViewGroup会继续往上派发, 23以及以下直接返回false
// 低于5.0的机器和RecyclerView配合工作时,部分机型会调用这个方法,但是ViewGroup并没有实现这个方法,会报错,这里catch一下
}
return false;
}
private int moveTargetView(float dy, boolean isDragging) {
int target = (int) (mTargetCurrentOffset + dy);
return moveTargetViewTo(target, isDragging);
}
private int moveTargetViewTo(int target, boolean isDragging) {
return moveTargetViewTo(target, isDragging, false);
}
private int moveTargetViewTo(int target, boolean isDragging, boolean calculateAnyWay) {
target = Math.max(target, mTargetInitOffset);
if (!mEnableOverPull) {
target = Math.min(target, mTargetRefreshOffset);
}
int offset = 0;
if (target != mTargetCurrentOffset || calculateAnyWay) {
offset = target - mTargetCurrentOffset;
ViewCompat.offsetTopAndBottom(mTargetView, offset);
mTargetCurrentOffset = target;
int total = mTargetRefreshOffset - mTargetInitOffset;
if (isDragging) {
mIRefreshView.onPull(Math.min(mTargetCurrentOffset - mTargetInitOffset, total), total,
mTargetCurrentOffset - mTargetRefreshOffset);
}
onMoveTargetView(mTargetCurrentOffset);
if (mListener != null) {
mListener.onMoveTarget(mTargetCurrentOffset);
}
if (mRefreshOffsetCalculator == null) {
mRefreshOffsetCalculator = new QMUIDefaultRefreshOffsetCalculator();
}
int newRefreshOffset = mRefreshOffsetCalculator.calculateRefreshOffset(mRefreshInitOffset, mRefreshEndOffset, mRefreshView.getHeight(),
mTargetCurrentOffset, mTargetInitOffset, mTargetRefreshOffset);
if (newRefreshOffset != mRefreshCurrentOffset) {
ViewCompat.offsetTopAndBottom(mRefreshView, newRefreshOffset - mRefreshCurrentOffset);
mRefreshCurrentOffset = newRefreshOffset;
onMoveRefreshView(mRefreshCurrentOffset);
if (mListener != null) {
mListener.onMoveRefreshView(mRefreshCurrentOffset);
}
}
}
return offset;
}
private void acquireVelocityTracker(final MotionEvent event) {
if (null == mVelocityTracker) {
mVelocityTracker = VelocityTracker.obtain();
}
mVelocityTracker.addMovement(event);
}
private void releaseVelocityTracker() {
if (null != mVelocityTracker) {
mVelocityTracker.clear();
mVelocityTracker.recycle();
mVelocityTracker = null;
}
}
public int getRefreshInitOffset() {
return mRefreshInitOffset;
}
public int getRefreshEndOffset() {
return mRefreshEndOffset;
}
public int getTargetInitOffset() {
return mTargetInitOffset;
}
public int getTargetRefreshOffset() {
return mTargetRefreshOffset;
}
public void setTargetRefreshOffset(int targetRefreshOffset) {
mEqualTargetRefreshOffsetToRefreshViewHeight = false;
mTargetRefreshOffset = targetRefreshOffset;
}
public View getTargetView() {
return mTargetView;
}
protected void onMoveTargetView(int offset) {
}
protected void onMoveRefreshView(int offset) {
}
private boolean hasFlag(int flag) {
return (mScrollFlag & flag) == flag;
}
private void removeFlag(int flag) {
mScrollFlag = mScrollFlag & ~flag;
}
@Override
public void computeScroll() {
if (mScroller.computeScrollOffset()) {
int offsetY = mScroller.getCurrY();
moveTargetViewTo(offsetY, false);
if (offsetY <= 0 && hasFlag(FLAG_NEED_DELIVER_VELOCITY)) {
deliverVelocity();
mScroller.forceFinished(true);
}
invalidate();
} else if (hasFlag(FLAG_NEED_SCROLL_TO_INIT_POSITION)) {
removeFlag(FLAG_NEED_SCROLL_TO_INIT_POSITION);
if (mTargetCurrentOffset != mTargetInitOffset) {
mScroller.startScroll(0, mTargetCurrentOffset, 0, mTargetInitOffset - mTargetCurrentOffset);
}
invalidate();
} else if (hasFlag(FLAG_NEED_SCROLL_TO_REFRESH_POSITION)) {
removeFlag(FLAG_NEED_SCROLL_TO_REFRESH_POSITION);
if (mTargetCurrentOffset != mTargetRefreshOffset) {
mScroller.startScroll(0, mTargetCurrentOffset, 0, mTargetRefreshOffset - mTargetCurrentOffset);
} else {
moveTargetViewTo(mTargetRefreshOffset, false, true);
}
invalidate();
} else if (hasFlag(FLAG_NEED_DO_REFRESH)) {
removeFlag(FLAG_NEED_DO_REFRESH);
onRefresh();
moveTargetViewTo(mTargetRefreshOffset, false, true);
} else {
deliverVelocity();
}
}
private void deliverVelocity() {
if (hasFlag(FLAG_NEED_DELIVER_VELOCITY)) {
removeFlag(FLAG_NEED_DELIVER_VELOCITY);
if (mScroller.getCurrVelocity() > mMiniVelocity) {
info("deliver velocity: " + mScroller.getCurrVelocity());
// if there is a velocity, pass it on
if (mTargetView instanceof RecyclerView) {
((RecyclerView) mTargetView).fling(0, (int) mScroller.getCurrVelocity());
} else if (mTargetView instanceof AbsListView && android.os.Build.VERSION.SDK_INT >= 21) {
((AbsListView) mTargetView).fling((int) mScroller.getCurrVelocity());
}
}
}
}
private void info(String msg) {
if (BuildConfig.DEBUG) {
Log.i(TAG, msg);
}
}
@Override
public boolean dispatchTouchEvent(MotionEvent ev) {
final int action = ev.getAction();
if (action == MotionEvent.ACTION_DOWN) {
mNestScrollDurationRefreshing = mIsRefreshing || (mScrollFlag & FLAG_NEED_DO_REFRESH) != 0;
} else if (mNestScrollDurationRefreshing) {
if (action == MotionEvent.ACTION_MOVE) {
if (!mIsRefreshing && mScroller.isFinished() && mScrollFlag == 0) {
// 这里必须要 dispatch 一次 down 事件,否则不能触发 NestScroll,具体可参考 RecyclerView
// down 过程中会触发 onStopNestedScroll,mNestScrollDurationRefreshing 必须在之后
// 置为false,否则会触发 finishPull
ev.offsetLocation(0, -mSystemTouchSlop-1);
ev.setAction(MotionEvent.ACTION_DOWN);
super.dispatchTouchEvent(ev);
mNestScrollDurationRefreshing = false;
ev.setAction(action);
// offset touch slop, 避免触发点击事件
ev.offsetLocation(0, mSystemTouchSlop+1);
}
} else {
mNestScrollDurationRefreshing = false;
}
}
return super.dispatchTouchEvent(ev);
}
public interface OnPullListener {
void onMoveTarget(int offset);
void onMoveRefreshView(int offset);
void onRefresh();
}
public interface OnChildScrollUpCallback {
boolean canChildScrollUp(QMUIPullRefreshLayout parent, @Nullable View child);
}
public interface RefreshOffsetCalculator {
int calculateRefreshOffset(int refreshInitOffset, int refreshEndOffset, int refreshViewHeight,
int targetCurrentOffset, int targetInitOffset, int targetRefreshOffset);
}
public interface IRefreshView {
void stop();
void doRefresh();
void onPull(int offset, int total, int overPull);
}
核心类:
QMUIPullRefreshLayout继承自ViewGroup实现NestedScrollingParent
NestedScrollingParent,NestedScrollingChild,Android 就是通过这两个接口, 来实现 子View 与父View 之间的嵌套滑动。
就是NestedScrollingParent内部的View,在滑动到时候,会首先将dx、dy传入给NestedScrollingParent,NestedScrollingParent可以决定是否对其进行消耗,一般会根据需求消耗部分或者全部
事件分发是这样的:子View首先得到事件处理权,处理过程中,父View可以对其拦截,但是拦截了以后就无法再还给子View(本次手势内)。
通过 targetView 的当前位置、targetView 的初始和刷新位置以及 refreshView 的初始与结束位置计算 RefreshView 的位置。
@Widget(widgetClass = QMUIProgressBar.class, iconRes = R.mipmap.icon_grid_progress_bar)
public class QDProgressBarFragment extends BaseFragment {
protected static final int STOP = 0x10000;
protected static final int NEXT = 0x10001;
@BindView(R.id.topbar)
QMUITopBarLayout mTopBar;
@BindView(R.id.rectProgressBar)
QMUIProgressBar mRectProgressBar;
@BindView(R.id.circleProgressBar)
QMUIProgressBar mCircleProgressBar;
@BindView(R.id.startBtn)
Button mStartBtn;
@BindView(R.id.backBtn)
Button mBackBtn;
int count;
private QDItemDescription mQDItemDescription;
private ProgressHandler myHandler = new ProgressHandler();
@Override
protected View onCreateView() {
View root = LayoutInflater.from(getActivity()).inflate(R.layout.fragment_progressbar, null);
ButterKnife.bind(this, root);
mQDItemDescription = QDDataManager.getInstance().getDescription(this.getClass());
initTopBar();
mRectProgressBar.setQMUIProgressBarTextGenerator(new QMUIProgressBar.QMUIProgressBarTextGenerator() {
@Override
public String generateText(QMUIProgressBar progressBar, int value, int maxValue) {
return value + "/" + maxValue;
}
});
mCircleProgressBar.setQMUIProgressBarTextGenerator(new QMUIProgressBar.QMUIProgressBarTextGenerator() {
@Override
public String generateText(QMUIProgressBar progressBar, int value, int maxValue) {
return 100 * value / maxValue + "%";
}
});
myHandler.setProgressBar(mRectProgressBar, mCircleProgressBar);
mStartBtn.setOnClickListener(new View.OnClickListener() {
@Override
public void onClick(View v) {
count = 0;
new Thread(new Runnable() {
@Override
public void run() {
for (int i = 0; i <= 20; i++) {
try {
count = (i + 1) * 5;
if (i == 20) {
Message msg = new Message();
msg.what = STOP;
myHandler.sendMessage(msg);
} else {
Message msg = new Message();
msg.what = NEXT;
msg.arg1 = count;
myHandler.sendMessage(msg);
}
} catch (Exception e) {
e.printStackTrace();
}
}
}
}).start();
}
});
mBackBtn.setOnClickListener(new View.OnClickListener() {
@Override
public void onClick(View v) {
mRectProgressBar.setProgress(0);
mCircleProgressBar.setProgress(0);
}
});
return root;
}
private void initTopBar() {
mTopBar.addLeftBackImageButton().setOnClickListener(new View.OnClickListener() {
@Override
public void onClick(View v) {
popBackStack();
}
});
mTopBar.setTitle(mQDItemDescription.getName());
}
private static class ProgressHandler extends Handler {
private WeakReference<QMUIProgressBar> weakRectProgressBar;
private WeakReference<QMUIProgressBar> weakCircleProgressBar;
void setProgressBar(QMUIProgressBar rectProgressBar, QMUIProgressBar circleProgressBar) {
weakRectProgressBar = new WeakReference<>(rectProgressBar);
weakCircleProgressBar = new WeakReference<>(circleProgressBar);
}
@Override
public void handleMessage(Message msg) {
super.handleMessage(msg);
switch (msg.what) {
case STOP:
break;
case NEXT:
if (!Thread.currentThread().isInterrupted()) {
if (weakRectProgressBar.get() != null && weakCircleProgressBar.get() != null) {
weakRectProgressBar.get().setProgress(msg.arg1);
weakCircleProgressBar.get().setProgress(msg.arg1);
} } } } }}
核心类 :QDProgressBarFragment继承自BaseFragment
运用多线程。
// 动态修改效果按钮
mTopBar.addRightImageButton(R.mipmap.icon_topbar_overflow, R.id.topbar_right_change_button)
.setOnClickListener(new View.OnClickListener() {
@Override
public void onClick(View view) {
showBottomSheetList();
}
});
}
private void reset() {
mRadiusImageView.setBorderColor(ContextCompat.getColor(getContext(), R.color.radiusImageView_border_color));
mRadiusImageView.setBorderWidth(QMUIDisplayHelper.dp2px(getContext(), 2));
mRadiusImageView.setCornerRadius(QMUIDisplayHelper.dp2px(getContext(), 10));
mRadiusImageView.setSelectedMaskColor(ContextCompat.getColor(getContext(), R.color.radiusImageView_selected_mask_color));
mRadiusImageView.setSelectedBorderColor(ContextCompat.getColor(getContext(), R.color.radiusImageView_selected_border_color));
mRadiusImageView.setSelectedBorderWidth(QMUIDisplayHelper.dp2px(getContext(), 3));
mRadiusImageView.setTouchSelectModeEnabled(true);
mRadiusImageView.setCircle(false);
mRadiusImageView.setOval(false);
}
private void showBottomSheetList() {
new QMUIBottomSheet.BottomListSheetBuilder(getActivity())
.addItem(getResources().getString(R.string.circularImageView_modify_1))
.addItem(getResources().getString(R.string.circularImageView_modify_2))
.addItem(getResources().getString(R.string.circularImageView_modify_3))
.addItem(getResources().getString(R.string.circularImageView_modify_4))
.addItem(getResources().getString(R.string.circularImageView_modify_5))
.addItem(getResources().getString(R.string.circularImageView_modify_6))
.addItem(getResources().getString(R.string.circularImageView_modify_7))
.addItem(getResources().getString(R.string.circularImageView_modify_8))
.addItem(getResources().getString(R.string.circularImageView_modify_9))
.setOnSheetItemClickListener(new QMUIBottomSheet.BottomListSheetBuilder.OnSheetItemClickListener() {
@Override
public void onClick(QMUIBottomSheet dialog, View itemView, int position, String tag) {
dialog.dismiss();
reset();
switch (position) {
case 0:
mRadiusImageView.setBorderColor(Color.BLACK);
mRadiusImageView.setBorderWidth(QMUIDisplayHelper.dp2px(getContext(), 4));
break;
case 1:
mRadiusImageView.setSelectedBorderWidth(QMUIDisplayHelper.dp2px(getContext(), 6));
mRadiusImageView.setSelectedBorderColor(Color.GREEN);
break;
case 2:
mRadiusImageView.setSelectedMaskColor(ContextCompat.getColor(getContext(), R.color.radiusImageView_selected_mask_color));
break;
case 3:
if (mRadiusImageView.isSelected()) {
mRadiusImageView.setSelected(false);
} else {
mRadiusImageView.setSelected(true);
}
break;
case 4:
mRadiusImageView.setCornerRadius(QMUIDisplayHelper.dp2px(getContext(), 20));
break;
case 5:
mRadiusImageView.setCircle(true);
break;
case 6:
mRadiusImageView.setOval(true);
case 7:
mRadiusImageView.setTouchSelectModeEnabled(false);
default:
break;
}
}
})
.build()
.show() }}
@Widget(widgetClass = QMUILinkTextView.class, iconRes = R.mipmap.icon_grid_link_text_view)
public class QDLinkTextViewFragment extends BaseFragment {
@BindView(R.id.topbar) QMUITopBar mTopBar;
@BindView(R.id.link_text_view) QMUILinkTextView mLinkTextView;
@Override
protected View onCreateView() {
View view = LayoutInflater.from(getContext()).inflate(R.layout.fragment_link_texview_layout, null);
ButterKnife.bind(this, view);
initTopBar();
mLinkTextView.setOnLinkClickListener(mOnLinkClickListener);
mLinkTextView.setOnLinkLongClickListener(new QMUILinkTextView.OnLinkLongClickListener() {
@Override
public void onLongClick(String text) {
Toast.makeText(getContext(), "long click: " + text, Toast.LENGTH_SHORT).show();
}
});
mLinkTextView.setOnClickListener(new View.OnClickListener() {
@Override
public void onClick(View v) {
Toast.makeText(getContext(), "click TextView", Toast.LENGTH_SHORT).show();
}
return view;
}
private void initTopBar() {
mTopBar.setTitle(QDDataManager.getInstance().getDescription(this.getClass()).getName());
mTopBar.addLeftBackImageButton().setOnClickListener(new View.OnClickListener() {
@Override
public void onClick(View v) {
popBackStack();
}
});
}
private QMUILinkTextView.OnLinkClickListener mOnLinkClickListener = new QMUILinkTextView.OnLinkClickListener() {
@Override
public void onTelLinkClick(String phoneNumber) {
Toast.makeText(getContext(), "识别到电话号码是:" + phoneNumber, Toast.LENGTH_SHORT).show();
}
@Override
public void onMailLinkClick(String mailAddress) {
Toast.makeText(getContext(), "识别到邮件地址是:" + mailAddress, Toast.LENGTH_SHORT).show();
}
@Override
public void onWebUrlLinkClick(String url) {
Toast.makeText(getContext(), "识别到网页链接是:" + url, Toast.LENGTH_SHORT).show();
}
};
}
QMUILinkTextView.java
public class QMUILinkTextView extends TextView implements QMUIOnSpanClickListener, ISpanTouchFix {
private static final String TAG = "LinkTextView";
private static final int MSG_CHECK_DOUBLE_TAP_TIMEOUT = 1000;
public static int AUTO_LINK_MASK_REQUIRED = QMUILinkify.PHONE_NUMBERS | QMUILinkify.EMAIL_ADDRESSES | QMUILinkify.WEB_URLS;
private static Set<String> AUTO_LINK_SCHEME_INTERRUPTED = new HashSet<>();
private CharSequence mOriginText = null;
static {
AUTO_LINK_SCHEME_INTERRUPTED.add("tel");
AUTO_LINK_SCHEME_INTERRUPTED.add("mailto");
AUTO_LINK_SCHEME_INTERRUPTED.add("http");
AUTO_LINK_SCHEME_INTERRUPTED.add("https");
}
/**
* 链接文字颜色
*/
private ColorStateList mLinkTextColor;
/**
* 链接背景颜色
*/
private ColorStateList mLinkBgColor;
private int mAutoLinkMaskCompat;
private OnLinkClickListener mOnLinkClickListener;
private OnLinkLongClickListener mOnLinkLongClickListener;
private boolean mNeedForceEventToParent = false;
/**
* 记录当前 Touch 事件对应的点是不是点在了 span 上面
*/
private boolean mTouchSpanHit;
private long mDownMillis = 0;
private static final long TAP_TIMEOUT = 200; // ViewConfiguration.getTapTimeout();
private static final long DOUBLE_TAP_TIMEOUT = ViewConfiguration.getDoubleTapTimeout();
public QMUILinkTextView(Context context) {
this(context, null);
mLinkBgColor = null;
mLinkTextColor = ContextCompat.getColorStateList(context, R.color.qmui_s_link_color);
}
public QMUILinkTextView(Context context, ColorStateList linkTextColor, ColorStateList linkBgColor) {
this(context, null);
mLinkBgColor = linkBgColor;
mLinkTextColor = linkTextColor;
}
public QMUILinkTextView(Context context, AttributeSet attrs) {
super(context, attrs);
mAutoLinkMaskCompat = getAutoLinkMask() | AUTO_LINK_MASK_REQUIRED;
setAutoLinkMask(0);
setMovementMethod(QMUILinkTouchMovementMethod.getInstance());
setHighlightColor(Color.TRANSPARENT);
TypedArray array = context.obtainStyledAttributes(attrs, R.styleable.QMUILinkTextView);
mLinkBgColor = array.getColorStateList(R.styleable.QMUILinkTextView_qmui_linkBackgroundColor);
mLinkTextColor = array.getColorStateList(R.styleable.QMUILinkTextView_qmui_linkTextColor);
array.recycle();
if (mOriginText != null) {
setText(mOriginText);
}
}
public void setOnLinkClickListener(OnLinkClickListener onLinkClickListener) {
mOnLinkClickListener = onLinkClickListener;
}
public void setOnLinkLongClickListener(OnLinkLongClickListener onLinkLongClickListener) {
mOnLinkLongClickListener = onLinkLongClickListener;
}
public int getAutoLinkMaskCompat() {
return mAutoLinkMaskCompat;
}
public void setAutoLinkMaskCompat(int mask) {
mAutoLinkMaskCompat = mask;
}
public void addAutoLinkMaskCompat(int mask) {
mAutoLinkMaskCompat |= mask;
}
public void removeAutoLinkMaskCompat(int mask) {
mAutoLinkMaskCompat &= ~mask;
}
/**
* 是否强制把TextView的事件强制传递给父元素。TextView在有ClickSpan的情况下默认会消耗掉事件
*
* @param needForceEventToParent true 为强制把TextView的事件强制传递给父元素,false 则不传递
*/
public void setNeedForceEventToParent(boolean needForceEventToParent) {
if (mNeedForceEventToParent != needForceEventToParent) {
mNeedForceEventToParent = needForceEventToParent;
if (mOriginText != null) {
setText(mOriginText);
}
}
}
public void setLinkColor(ColorStateList linkTextColor) {
mLinkTextColor = linkTextColor;
}
@Override
public void setText(CharSequence text, BufferType type) {
mOriginText = text;
if (!TextUtils.isEmpty(text)) {
SpannableStringBuilder builder = new SpannableStringBuilder(text);
QMUILinkify.addLinks(builder, mAutoLinkMaskCompat, mLinkTextColor, mLinkBgColor, this);
text = builder;
}
super.setText(text, type);
if (mNeedForceEventToParent && getLinksClickable()) {
setFocusable(false);
setClickable(false);
setLongClickable(false);
}
}
@Override
public boolean onSpanClick(String text) {
if (null == text) {
Log.w(TAG, "onSpanClick interrupt null text");
return true;
}
long clickUpTime = (SystemClock.uptimeMillis() - mDownMillis);
Log.w(TAG, "onSpanClick clickUpTime: " + clickUpTime);
if (mSingleTapConfirmedHandler.hasMessages(MSG_CHECK_DOUBLE_TAP_TIMEOUT)) {
disallowOnSpanClickInterrupt();
return true;
}
if (TAP_TIMEOUT < clickUpTime) {
Log.w(TAG, "onSpanClick interrupted because of TAP_TIMEOUT: " + clickUpTime);
return true;
}
String scheme = Uri.parse(text).getScheme();
if (scheme != null) {
scheme = scheme.toLowerCase();
}
if (AUTO_LINK_SCHEME_INTERRUPTED.contains(scheme)) {
long waitTime = DOUBLE_TAP_TIMEOUT - clickUpTime;
mSingleTapConfirmedHandler.removeMessages(MSG_CHECK_DOUBLE_TAP_TIMEOUT);
Message msg = Message.obtain();
msg.what = MSG_CHECK_DOUBLE_TAP_TIMEOUT;
msg.obj = text;
mSingleTapConfirmedHandler.sendMessageDelayed(msg, waitTime);
return true;
}
return false;
}
@SuppressLint("ClickableViewAccessibility")
@Override
public boolean onTouchEvent(MotionEvent event) {
switch (event.getAction() & MotionEvent.ACTION_MASK) {
case MotionEvent.ACTION_DOWN:
boolean hasSingleTap = mSingleTapConfirmedHandler.hasMessages(MSG_CHECK_DOUBLE_TAP_TIMEOUT);
Log.w(TAG, "onTouchEvent hasSingleTap: " + hasSingleTap);
if (!hasSingleTap) {
mDownMillis = SystemClock.uptimeMillis();
} else {
Log.w(TAG, "onTouchEvent disallow onSpanClick mSingleTapConfirmedHandler because of DOUBLE TAP");
disallowOnSpanClickInterrupt();
}
break;
}
boolean ret = super.onTouchEvent(event);
if (mNeedForceEventToParent) {
return mTouchSpanHit;
}
return ret;
}
private void disallowOnSpanClickInterrupt() {
mSingleTapConfirmedHandler.removeMessages(MSG_CHECK_DOUBLE_TAP_TIMEOUT);
mDownMillis = 0;
}
protected boolean performSpanLongClick(String text) {
if (mOnLinkLongClickListener != null) {
mOnLinkLongClickListener.onLongClick(text);
return true;
}
return false;
}
@Override
public boolean performClick() {
if (!mTouchSpanHit && !mNeedForceEventToParent) {
return super.performClick();
}
return false;
}
@Override
public boolean performLongClick() {
int end = getSelectionEnd();
if (end > 0) {
String selectStr = getText().subSequence(getSelectionStart(), end).toString();
return performSpanLongClick(selectStr) || super.performLongClick();
}
return super.performLongClick();
}
private Handler mSingleTapConfirmedHandler = new Handler(Looper.getMainLooper()) {
public void handleMessage(android.os.Message msg) {
if (MSG_CHECK_DOUBLE_TAP_TIMEOUT != msg.what) {
return;
}
Log.d(TAG, "handleMessage: " + msg.obj);
if (msg.obj instanceof String) {
String url = (String) msg.obj;
if (null != mOnLinkClickListener && !TextUtils.isEmpty(url)) {
String schemeUrl = url.toLowerCase();
if (schemeUrl.startsWith("tel:")) {
String phoneNumber = Uri.parse(url).getSchemeSpecificPart();
mOnLinkClickListener.onTelLinkClick(phoneNumber);
} else if (schemeUrl.startsWith("mailto:")) {
String mailAddr = Uri.parse(url).getSchemeSpecificPart();
mOnLinkClickListener.onMailLinkClick(mailAddr);
} else if (schemeUrl.startsWith("http") || schemeUrl.startsWith("https")) {
mOnLinkClickListener.onWebUrlLinkClick(url);
}
}
}
}
};
@Override
public void setTouchSpanHit(boolean hit) {
if (mTouchSpanHit != hit) {
mTouchSpanHit = hit;
}
}
public interface OnLinkClickListener {
/**
* 电话号码被点击
*/
void onTelLinkClick(String phoneNumber);
/**
* 邮箱地址被点击
*/
void onMailLinkClick(String mailAddress);
/**
* URL 被点击
*/
void onWebUrlLinkClick(String url);
}
public interface OnLinkLongClickListener {
void onLongClick(String text);
}
}
使 {@link android.widget.TextView} 能自动识别 URL、电话、邮箱地址。
相比于 {@link android.widget.TextView} 使用{@linkandroid.text.util.Linkify}, {@link QMUILinkTextView} 有以下特点:
可以通过 {@link com.qmuiteam.qmui.R.styleable#QMUILinkTextView} 中的属性设置链接的样式,
可以通过 {@linkQMUILinkTextView#setOnLinkClickListener(QMUILinkTextView.OnLinkClickListener)} 设置链接的点击事件,而不是 {@link android.widget.TextView} 默认的 {@link android.content.Intent} 跳转
核心类:QMUILinkTextView继承TextView,能够识别电话号码邮箱地址等
https://www.jianshu.com/p/d3bef8449960
Context类 路径: /frameworks/base/core/java/android/content/Context.java
说明: 抽象类,提供了一组通用的API。
源代码(部分)如下:
public abstract class Context {
…
public abstract Object getSystemService(String name); //获得系统级服务
public abstract void startActivity(Intent intent); //通过一个Intent启动Activity
public abstract ComponentName startService(Intent service); //启动Service
//根据文件名得到SharedPreferences对象
public abstract SharedPreferences getSharedPreferences(String name,int mode);
…
}
1、它描述的是一个应用程序环境的信息,即上下文。
2、该类是一个抽象(abstract class)类,Android提供了该抽象类的具体实现类(后面我们会讲到是ContextIml类)。
3、通过它我们可以获取应用程序的资源和类,也包括一些应用级别操作,例如:启动一个Activity,发送广播,接受Intent信息等