一种完美解决Android TV 焦点的自定义控件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属性的作用

  • 1
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

Mrsongs的心情杂货铺

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值