解决焦点问题的自定义控件CheckTextView
前言
前段时间设计部提供过来的UI需要将一个CheckBox控件和TextView 控件作为一个整体在选中的时候获取焦点背景为白色。当时做的自定义控件焦点出了问题花了很多时间还没有搞定,最后不得已采用了button+drawable的方式。后来自己还是研究出来了自定义控件控制焦点的方式,本篇博客将这两种方案都写下来,也作为一种学习记录。
效果展示
下图是两种方式的效果图
方式一 : button+ drawable
这种方式就是上述效果图中的第二种了。这个是一个button,代码中设置drawable 的形式。非常的简单,而且本身就是一个button 不用考虑焦点的问题。我们根据selector文件和 android:state_focused 的状态就可以设置字体颜色和背景。我们先看布局文件中的写法:
<Button
android:id="@+id/bt_reset_factory"
android:layout_width="420px"
android:layout_height="90px"
android:layout_marginTop="20px"
android:layout_centerInParent="true"
android:layout_below="@+id/reset_factory"
android:background="@drawable/checkbutton_bg_selector"
android:drawablePadding="15px"
android:paddingLeft="30px"
android:text="button+drawable"
android:textColor="@color/checkbutton_text_selector"
android:visibility="visible" />
这里面的白色背景是根据android:background="@drawable/checkbutton_bg_selector
来设置的。
首先我们在drawable下新建一个checkbutton_bg_selector.xml
文件
然后在里面根据焦点的状态:android:state_focused来设置不同状态下的背景图片就可以了,代码如下:
<?xml version="1.0" encoding="utf-8"?>
<selector xmlns:android="http://schemas.android.com/apk/res/android" >
<item android:drawable="@drawable/factory_select_on" android:state_focused="true"/>
<item android:drawable="@drawable/factory_select_off" android:state_focused="false"/>
</selector>
然后怎么在button图片中设置drawable图片呢,之前有用过drawableleft 属性但那个是分开的两个控件和这个不同。
这里面有两个属性很重要:
android:drawablePadding="15px" android:paddingLeft="30px"
在代码中设置button.setCompoundDrawables
方法去设置button 中嵌入drawable 图片,这样子就相当于一个button,可以避开焦点的问题(因为Button是默认获取焦点的)
Drawable eyeDrawable = getResources().getDrawable(R.drawable.checkbutton_selector_off);
eyeDrawable.setBounds(0, 0, 60, 60);
button.setCompoundDrawables(eyeDrawable, null, null, null);
不同状态下点击状态进行动态设置
if (isButtonChecked){
isButtonChecked=false;
Drawable eyeDrawable = getResources().getDrawable(R.drawable.checkbutton_selector_off);
eyeDrawable.setBounds(0, 0, 60, 60);
button.setCompoundDrawables(eyeDrawable, null, null, null);
}else {
isButtonChecked=true;
Drawable eyeDrawable = getResources().getDrawable(R.drawable.checkbutton_selector_on);
eyeDrawable.setBounds(0, 0, 60, 60);
button.setCompoundDrawables(eyeDrawable, null, null, null);
}
这样的方式是可以的,简单实用,但是场景应用很有局限性,TV开发中很多地方都有用到自定义控件,如果稍微复杂点,控件多一点的话这种的方法是不能满足业务需求的。
方法二、定义一种attr属性的自定义控件 checkTextView
该自定义控件的核心是用根布局获得焦点,子控件都没有焦点,子控件根据定义的 select_state
属性去相应的改变背景状态等等。步骤如下:
一、定义attrs.xml 中定义属性 select_state
<!-- item选中状态自定义属性-->
<declare-styleable name="ItemStatus">
<attr name="select_state" format="boolean" />
</declare-styleable>
二、使用select_state
在drawable.xml中添加背景图片和字体颜色的selector
<?xml version="1.0" encoding="utf-8"?>
<selector xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:app="http://schemas.android.com/apk/res-auto">
<item android:drawable="@drawable/checkbox3" app:select_state="true" />
<item android:drawable="@drawable/checkbox4" app:select_state="false" />
</selector>
注意我们这里使用的是我们定义的 select_state
区分选择状态,而不是原生的 android:state_focused="true"
,颜色选择器也用这种状态。
三、定义SelectStateLayout
去刷新UI
package com.xxx.checktextveiw;
import android.content.Context;
import android.util.AttributeSet;
import android.widget.RelativeLayout;
import com.xxx.checktextveiw.R;
/**
* Created by mrsongs on 2018/7/4.
*/
public class SelectStateLayout extends RelativeLayout {
private boolean select;
private static final int[] MY_STATE = {R.attr.select_state};
private boolean shakeEnable = false;
public SelectStateLayout(Context context) {
super(context);
}
public SelectStateLayout(Context context, AttributeSet attrs) {
super(context, attrs);
}
public SelectStateLayout(Context context, AttributeSet attrs, int defStyleAttr) {
super(context, attrs, defStyleAttr);
}
public void setSelect(boolean select) {
if (this.select != select) {
this.select = select;
refreshDrawableState();//刷新状态
}
}
public void setShakeEnable(boolean shakeEnable) {
this.shakeEnable = shakeEnable;
}
@Override
protected int[] onCreateDrawableState(int extraSpace) {
final int[] drawableState = super.onCreateDrawableState(extraSpace + 1);
if (select) {
mergeDrawableStates(drawableState, MY_STATE);
}
return drawableState;
}
public boolean isSelect() {
return select;
}
}
四、自定义控件checkTextView的布局文件
我们在根布局设置 android:focusable="true"
,子控件都设置属性 android:duplicateParentState="true"
和 android:focusable="false"
这样就只有根布局才有焦点,子控件默认是没有焦点的。
<?xml version="1.0" encoding="utf-8"?>
<com.xxx.checktextveiw.SelectStateLayout xmlns:android="http://schemas.android.com/apk/res/android"
android:id="@+id/resetView"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:focusable="true"
android:clickable="true"
android:background="@drawable/checkbutton_bg_selector">
<ImageView
android:id="@+id/iv_checkbox"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:src="@drawable/checkbutton_selector_on"
android:layout_centerVertical="true"
android:layout_marginStart="10px"
android:duplicateParentState="true"
android:focusableInTouchMode="false"
android:clickable="false"
android:focusable="false"/>
<TextView
android:id="@+id/tv_checktext"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:textSize="32px"
android:text="自定义CheckTextView"
android:focusable="false"
android:clickable="false"
android:textColor="@color/checkview_text_selector"
android:layout_toEndOf="@+id/iv_checkbox"
android:layout_centerVertical="true"
android:duplicateParentState="true"
android:layout_marginStart="20px"/>
</com.xxx.checktextveiw.SelectStateLayout>
五、代码中监听focus的状态,根据foucus的状态改变 select_state
@Override
public void onFocusChange(View view, boolean hasFocus) {
Log.d(TAG, "view=" + view + ",hasFocus=" + hasFocus);
if (view instanceof SelectStateLayout) {
setItemStatus((SelectStateLayout) view, hasFocus);
}
}
private void setItemStatus(final SelectStateLayout view, final boolean focus) {
view.setSelect(focus);
}
完整代码
CheckTextView.java
package com.xxx.checktextveiw;
import android.content.Context;
import android.os.Parcel;
import android.os.Parcelable;
import android.util.AttributeSet;
import android.util.Log;
import android.view.LayoutInflater;
import android.view.View;
import android.widget.ImageView;
import android.widget.LinearLayout;
import android.widget.TextView;
/**
* Created by songzhihao on 2018/9/5.
*/
public class CheckTextView extends LinearLayout implements View.OnFocusChangeListener {
private static final String TAG = "CheckTextView";
private Context mContext;
private boolean isChecked;
private ImageView checkBox;
private TextView checkText;
private View resetFactoryView;
private OnCheckedChangeListener onCheckedChangeListener;
private OnViewClickedListener onViewClickedListener;
public CheckTextView(Context context) {
super(context);
Log.d(TAG,"---CheckTextView(Context context)");
//refreshState();
}
public CheckTextView(Context context, AttributeSet attrs) {
super(context, attrs);
//refreshState();
Log.d(TAG,"---CheckTextView(Context context, AttributeSet attrs)");
}
public CheckTextView(Context context, AttributeSet attrs, int defStyleAttr) {
super(context, attrs, defStyleAttr);
Log.d(TAG,"---CheckTextView(Context context, AttributeSet attrs, int defStyleAttr)");
// refreshState();
}
@Override
protected void onFinishInflate() {
Log.d(TAG,"---onFinishInflate---");
super.onFinishInflate();
LayoutInflater.from(getContext()).inflate(R.layout.checkboxview, this, true);
checkBox = (ImageView) findViewById(R.id.iv_checkbox);
checkText = (TextView) findViewById(R.id.tv_checktext);
resetFactoryView=findViewById(R.id.resetView);
resetFactoryView.setOnFocusChangeListener(this);
resetFactoryView.setOnClickListener(new OnClickListener() {
@Override
public void onClick(View v) {
Log.d(TAG,"v="+v.getId());
onViewClickedListener.onViewClicked(v);
}
});
}
public void setChecked(boolean open) {
Log.d(TAG,"--setChecked-- open="+open);
isChecked = open;
refreshState();
if (onCheckedChangeListener != null) {
onCheckedChangeListener.onCheckedChanged(this, isChecked);
}
}
@Override
public void onFocusChange(View view, boolean hasFocus) {
Log.d(TAG, "view=" + view + ",hasFocus=" + hasFocus);
if (view instanceof SelectStateLayout) {
setItemStatus((SelectStateLayout) view, hasFocus);
}
}
private void setItemStatus(final SelectStateLayout view, final boolean focus) {
view.setSelect(focus);
}
private void refreshState() {
Log.d(TAG,"--refreshState--isChecked="+isChecked);
if (isChecked) {
checkBox.setImageResource(R.drawable.checkview_selector_on);
} else {
checkBox.setImageResource(R.drawable.checkview_selector_off);
}
}
public void setOnCheckedChangeListener(CheckTextView.OnCheckedChangeListener onCheckedChangeListener) {
this.onCheckedChangeListener = onCheckedChangeListener;
}
public void setOnViewClickedListener(OnViewClickedListener listener){
this.onViewClickedListener=listener;
}
public interface OnCheckedChangeListener {
void onCheckedChanged(CheckTextView View, boolean isChecked);
}
public interface OnViewClickedListener{
void onViewClicked(View view);
}
@Override
protected Parcelable onSaveInstanceState() {
Log.e(TAG, "onSave");
Parcelable superState = super.onSaveInstanceState();
CheckTextView.SavedState ss = new CheckTextView.SavedState(superState);
ss.isOpen = isChecked ? 1 : 0;
return ss;
}
@Override
protected void onRestoreInstanceState(Parcelable state) {
Log.e(TAG, "onRestore");
CheckTextView.SavedState ss = (CheckTextView.SavedState) state;
super.onRestoreInstanceState(state);
boolean result = ss.isOpen == 1;
setChecked(result);
}
static class SavedState extends BaseSavedState {
int isOpen;
public SavedState(Parcel source) {
super(source);
isOpen = source.readInt();
}
public SavedState(Parcelable superState) {
super(superState);
}
@Override
public void writeToParcel(Parcel out, int flags) {
super.writeToParcel(out, flags);
out.writeInt(isOpen);
}
public static final Creator<SavedState> CREATOR
= new Creator<SavedState>() {
@Override
public CheckTextView.SavedState createFromParcel(Parcel source) {
return new CheckTextView.SavedState(source);
}
@Override
public CheckTextView.SavedState[] newArray(int size) {
return new CheckTextView.SavedState[0];
}
};
}
}
MainActivity.java
package com.xxx.checktextveiw;
import android.content.Context;
import android.content.SharedPreferences;
import android.content.pm.PackageManager;
import android.graphics.drawable.Drawable;
import android.os.Bundle;
import android.support.v7.app.AppCompatActivity;
import android.util.Log;
import android.view.View;
import android.widget.Button;
import android.widget.Toast;
public class MainActivity extends AppCompatActivity implements View.OnClickListener,CheckTextView.OnCheckedChangeListener ,CheckTextView.OnViewClickedListener{
private static final String TAG = "MainActivity";
private CheckTextView checkTextView;
private Button button;
private boolean isButtonChecked;
private boolean isViewChecked;
Context preferenceContext;
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
//自定义view
checkTextView = (CheckTextView) findViewById(R.id.reset_factory);
checkTextView.setOnClickListener(this);
checkTextView.setOnViewClickedListener(this);
checkTextView.setChecked(isViewChecked);
//button
button = findViewById(R.id.bt_reset_factory);
button.setOnClickListener(this);
Drawable eyeDrawable = getResources().getDrawable(R.drawable.checkbutton_selector_off);
eyeDrawable.setBounds(0, 0, 60, 60);
button.setCompoundDrawables(eyeDrawable, null, null, null);
//getPreference();
}
@Override
public void onClick(View v) {
Log.d(TAG,"--onclick--isChecked="+isButtonChecked+",v="+v.getId());
switch (v.getId()) {
//button
case R.id.bt_reset_factory:
if (isButtonChecked){
isButtonChecked=false;
Drawable eyeDrawable = getResources().getDrawable(R.drawable.checkbutton_selector_off);
eyeDrawable.setBounds(0, 0, 60, 60);
button.setCompoundDrawables(eyeDrawable, null, null, null);
}else {
isButtonChecked=true;
Drawable eyeDrawable = getResources().getDrawable(R.drawable.checkbutton_selector_on);
eyeDrawable.setBounds(0, 0, 60, 60);
button.setCompoundDrawables(eyeDrawable, null, null, null);
}
// case R.id.reset_factory:
// Toast.makeText(this,"自定义控件被点击了",Toast.LENGTH_SHORT).show();
// break;
default:
break;
}
}
@Override
public void onCheckedChanged(CheckTextView View, boolean isChecked) {
if (isChecked){
//do something
}else {
//do something
}
}
@Override
public void onViewClicked(View view) {
Log.d(TAG,"view="+view);
checkTextView.setChecked(!isViewChecked);
isViewChecked=!isViewChecked;
}
private void getPreference(){
try {
preferenceContext=createPackageContext("xxx.com.demo1", Context.CONTEXT_IGNORE_SECURITY);
} catch (PackageManager.NameNotFoundException e) {
e.printStackTrace();
}
SharedPreferences preferences=preferenceContext.getSharedPreferences("szh", Context.MODE_WORLD_READABLE);
String a=preferences.getString("song","null");
Log.d("SZH","a="+a);
}
}
代码到这里就完了,各位读者如果有问题,欢迎评论区留言
延伸问题
1、duplicateParentState属性的作用