最近在重构公司app,产品经理设计了一种排序方式,类似火币pro行情页,京东行情页那种排序按钮,有三种状态,如下图:
排序按钮状态图
其中各个排序分类是互斥的,每个排序分类有三种状态:默认、升序、降序。
如果通过布局写的话也能实现,但是比较不方便,如果动态增加排序状态比较麻烦,所以给封装成了控件,仿照RadioGroup可以动态增加排序分类。
核心代码见:SortRadioGroup.java
public class SortRadioGroupextends RelativeLayout {
// holds the checked id; the selection is empty by default
private int mCheckedId = -1;
// tracks children radio buttons checked state
private SortRadioButton.OnCheckedChangeListenermChildOnCheckedChangeListener;
// when true, mOnCheckedChangeListener discards events
private boolean mProtectFromCheckedChange =false;
private OnCheckedChangeListenermOnCheckedChangeListener;
private PassThroughHierarchyChangeListenermPassThroughListener;
/**
* {@inheritDoc}
*/
public SortRadioGroup(Context context) {
super(context);
init();
}
/**
* {@inheritDoc}
*/
public SortRadioGroup(Context context, AttributeSet attrs) {
super(context, attrs);
init();
}
private void init() {
//setOrientation(HORIZONTAL);
mChildOnCheckedChangeListener =new CheckedStateTracker();
mPassThroughListener =new PassThroughHierarchyChangeListener();
super.setOnHierarchyChangeListener(mPassThroughListener);
}
/**
* {@inheritDoc}
*/
@Override
public void setOnHierarchyChangeListener(OnHierarchyChangeListener listener) {
// the user listener is delegated to our pass-through listener
mPassThroughListener.mOnHierarchyChangeListener = listener;
}
/**
* {@inheritDoc}
*/
@Override
protected void onFinishInflate() {
super.onFinishInflate();
// checks the appropriate radio button as requested in the XML file
if (mCheckedId != -1) {
mProtectFromCheckedChange =true;
setCheckedStateForView(mCheckedId,true);
mProtectFromCheckedChange =false;
}
}
@Override
public void addView(View child,int index, ViewGroup.LayoutParams params) {
if (childinstanceof SortRadioButton) {
final SortRadioButton button = (SortRadioButton) child;
if (button.isChecked()) {
mProtectFromCheckedChange =true;
if (mCheckedId != -1) {
setCheckedStateForView(mCheckedId,false);
}
mProtectFromCheckedChange =false;
}
}
super.addView(child, index, params);
}
/**
*
Sets the selection to the radio button whose identifier is passed in
* parameter. Using -1 as the selection identifier clears the selection;
* such an operation is equivalent to invoking {@link #clearCheck()}.
** @param id the unique id of the radio button to select in this group
* @see #getCheckedRadioButtonId()
* @see #clearCheck()
*/
public void check(int id) {
// don't even bother
if (id != -1 && (id ==mCheckedId)) {
return;
}
if (mCheckedId != -1) {
setCheckedStateForView(mCheckedId,false);
}
if (id != -1) {
setCheckedStateForView(id,true);
}
setCheckedId(id);
}
private void setCheckedId(int id) {
mCheckedId = id;
}
public OnCheckedChangeListener getOnCheckedChangeListener() {
return mOnCheckedChangeListener;
}
public interface OnCheckedChangeListener {
/**
*
Called when the checked radio button has changed. When the
* selection is cleared, checkedId is -1.
** @param group the group in which the checked radio button has changed
* @param checkedId the unique identifier of the newly checked radio button
*/
public void onCheckedChanged(SortRadioGroup group,@IdRes int checkedId,int orientation);
}
private void setCheckedStateForView(int viewId,boolean checked) {
View checkedView = findViewById(viewId);
if (checkedView !=null && checkedViewinstanceof SortRadioButton) {
SortRadioButton radioButton = (SortRadioButton) checkedView;
((SortRadioButton) checkedView).setChecked(checked, radioButton.isOrientation());
}
}
/**
*
Returns the identifier of the selected radio button in this group.
* Upon empty selection, the returned value is -1.
** @return the unique id of the selected radio button in this group
* @attr ref android.R.styleable#RadioGroup_checkedButton
* @see #clearCheck()
*/
public int getCheckedRadioButtonId() {
return mCheckedId;
}
/**
*
Clears the selection. When the selection is cleared, no radio button
* in this group is selected and {@link #getCheckedRadioButtonId()} returns
* null.
** @see #getCheckedRadioButtonId()
*/
public void clearCheck() {
check(-1);
}
/**
*
Register a callback to be invoked when the checked radio button
* changes in this group.
** @param listener the callback to call on checked state change
*/
public void setOnCheckedChangeListener(OnCheckedChangeListener listener) {
mOnCheckedChangeListener = listener;
}
private class CheckedStateTrackerimplements SortRadioButton.OnCheckedChangeListener {
public void onCheckedChanged(SortRadioButton buttonView,boolean isChecked) {
// prevents from infinite r ecursion
if (mProtectFromCheckedChange) {
return;
}
mProtectFromCheckedChange =true;
if (mCheckedId != -1) {
setCheckedStateForView(mCheckedId,false);
}
mProtectFromCheckedChange =false;
int id = buttonView.getId();
setCheckedId(id);
}
}
/**
*
A pass-through listener acts upon the events and dispatches them
* to another listener. This allows the table layout to set its own internal
* hierarchy change listener without preventing the user to setup his.
*/private class PassThroughHierarchyChangeListenerimplements
OnHierarchyChangeListener {
private OnHierarchyChangeListenermOnHierarchyChangeListener;
/**
* {@inheritDoc}
*/
public void onChildViewAdded(View parent, View child) {
if (parent == SortRadioGroup.this && childinstanceof SortRadioButton) {
int id = child.getId();
// generates an id if it's missing
if (id == View.NO_ID) {
throw new RuntimeException("SoftRadioButton must set Id");
}
((SortRadioButton) child).setOnCheckedChangeWidgetListener(mChildOnCheckedChangeListener);
}
if (mOnHierarchyChangeListener !=null) {
mOnHierarchyChangeListener.onChildViewAdded(parent, child);
}
}
/**
* {@inheritDoc}
*/
public void onChildViewRemoved(View parent, View child) {
if (mOnHierarchyChangeListener !=null) {
mOnHierarchyChangeListener.onChildViewRemoved(parent, child);
}
}
}
public interface SortCheckable {
/**
* @param checked true 或 false
* @param isOrientation 0默认 ,1上, 2下
*/
void setChecked(boolean checked,int isOrientation);
/**
* @return The current checked state of the view
*/
boolean isChecked();
/**
* Change the checked state of the view to the inverse of its current state
*/
void toggle();
}
}
核心代码见:SortRadioButton.java
public class SortRadioButtonextends FrameLayoutimplements SortRadioGroup.SortCheckable {
/**
* down false up true
*/
private boolean mBroadcasting;
private int orientation =0;
private TextViewmTv_checkable_text;
private ImageViewmIv_checkable_up;
private ImageViewmIv_checkable_down;
private OnCheckedChangeListenermOnCheckedChangeWidgetListener;
private int textColor = Color.BLACK;
private int textColorChecked = Color.RED;
private int upImageRes,upImageResChecked,downImageRes,downImageResChecked;
private SortRadioButton(Context context) {
super(context);
}
public SortRadioButton(Context context, AttributeSet attrs) {
super(context, attrs);
initView(attrs);
}
public SortRadioButton(Context context, AttributeSet attrs,int defStyleAttr) {
super(context, attrs, defStyleAttr);
initView(attrs);
}
private void initView(AttributeSet attrs) {
TypedArray ta = getContext().obtainStyledAttributes(attrs, R.styleable.SortRadioButton);
String text = ta.getString(R.styleable.SortRadioButton_text);
textColor = ta.getColor(R.styleable.SortRadioButton_textColor,textColor);
textColorChecked = ta.getColor(R.styleable.SortRadioButton_textColorChecked,textColorChecked);
upImageRes = ta.getResourceId(R.styleable.SortRadioButton_upImage,0);
downImageRes = ta.getResourceId(R.styleable.SortRadioButton_downImage,0);
upImageResChecked = ta.getResourceId(R.styleable.SortRadioButton_upImageChecked,0);
downImageResChecked = ta.getResourceId(R.styleable.SortRadioButton_downImageChecked,0);
ta.recycle();
LayoutInflater inflate = (LayoutInflater) getContext().getSystemService(Context.LAYOUT_INFLATER_SERVICE);
View view = inflate.inflate(R.layout.view_soft_item,this,true);
mTv_checkable_text = (TextView) findViewById(R.id.tv_checkable_text);
mIv_checkable_up = (ImageView) findViewById(R.id.iv_checkable_up);
mIv_checkable_down = (ImageView) findViewById(R.id.iv_checkable_down);
mTv_checkable_text.setText(text);
refreshView();
setOnClickListener(null);
}
private boolean ischecked;
public void setText(CharSequence text) {
mTv_checkable_text.setText(text);
}
public CharSequence getText() {
return mTv_checkable_text.getText();
}
private OnClickListenerml;
@Override
public void setOnClickListener(@Nullable OnClickListener l) {
ml = l;
super.setOnClickListener(new OnClickListener() {
@Override
public void onClick(View v) {
if (ml !=null) {
ml.onClick(v);
}
if (orientation ==0) {
orientation =1;
}else if (orientation ==1) {
orientation =2;
}else {
orientation =0;
}
if (isChecked()) {
setChecked(true,orientation);
}else {
setChecked(true,orientation);
}
refreshView();
}
});
}
private SortRadioGroupsoftGroup;
public SortRadioGroup getGroup() {
if (softGroup ==null) {
if (getParent() !=null && getParent()instanceof SortRadioGroup) {
softGroup = (SortRadioGroup) getParent();
}
}
return softGroup;
}
@Override
public void setChecked(boolean checked,int isOrientation) {
if (checked) {
makeCallBack(isOrientation);
}
if (ischecked != checked) {//刷新其他的
ischecked = checked;
refreshView();
if (mBroadcasting) {
return;
}
mBroadcasting =true;
if (mOnCheckedChangeWidgetListener !=null) {
mOnCheckedChangeWidgetListener.onCheckedChanged(this,ischecked);
}
mBroadcasting =false;
}else {
if (isOrientation !=orientation) {
refreshView();
if (mBroadcasting) {
return;
}
mBroadcasting =true;
if (mOnCheckedChangeWidgetListener !=null) {
mOnCheckedChangeWidgetListener.onCheckedChanged(this,ischecked);
}
mBroadcasting =false;
}
}
}
private void makeCallBack(int isOrientation) {
SortRadioGroup group = getGroup();
if (group !=null && group.getOnCheckedChangeListener() !=null) {
group.getOnCheckedChangeListener().onCheckedChanged(group, getId(), isOrientation);
}
}
@Override
public boolean isChecked() {
return ischecked;
}
public int isOrientation() {
return orientation;
}
@Override
public void toggle() {
ischecked = !ischecked;
refreshView();
}
private void refreshView() {
if (isChecked()) {
if (orientation ==0) {
mIv_checkable_up.setImageResource(upImageRes);
mIv_checkable_down.setImageResource(downImageRes);
}else if (orientation ==1) {
mIv_checkable_up.setImageResource(upImageResChecked);
mIv_checkable_down.setImageResource(downImageRes);
}else {
mIv_checkable_up.setImageResource(upImageRes);
mIv_checkable_down.setImageResource(downImageResChecked);
}
mTv_checkable_text.setTextColor(textColorChecked);
}else {
orientation =0;
mIv_checkable_up.setImageResource(upImageRes);
mIv_checkable_down.setImageResource(downImageRes);
mTv_checkable_text.setTextColor(textColor);
}
}
public void setOnCheckedChangeWidgetListener(OnCheckedChangeListener onCheckedChangeWidgetListener) {
mOnCheckedChangeWidgetListener = onCheckedChangeWidgetListener;
}
public interface OnCheckedChangeListener {
/**
* Called when the checked state of a compound button has changed.
*
* @param buttonView The compound button view whose state has changed.
* @param isChecked The new checked state of buttonView.
*/
void onCheckedChanged(SortRadioButton buttonView,boolean isChecked);
}
}
用法跟radiogroup+radiobutton一样,使用setOnCheckedChangeListener方法,获取切换的事件,orientation代表状态0、1、2分别是默认,升序,降序。
仓促之间写的代码,不足之处请指正。稍后会将代码同步到github。