解除SwitchPreference与preference 的绑定事件及单独调用Switch控件

本文介绍了一种在Android设备上实现以太网设置的方法,包括创建自定义SwitchPreference组件,用于控制DHCP服务器的开关状态,并解决了在PreferenceFragment中初始化控件的问题。

应用场景:

公司要求在设备上添加一个以太网的视频网卡,我负责在Setting里添加一个UI可以对DHCP服务器进行开或者关的操作。

首先我定义了一个EthernetSettings 继承 SettingsPreferenceFragment,用addPreferencesFromResource(R.xml.ethernet_settings) 加载布局文件。

<?xml version="1.0" encoding="utf-8"?>
<PreferenceScreen xmlns:android="http://schemas.android.com/apk/res/android"
    android:title="@string/ethernet_settings" >

    <Preference
        android:key="local_connection_key"
        android:title="@string/local_connection" >
    </Preference>

    <PreferenceCategory android:title="@string/lan_configuration" >
        <com.android.settings.ethernet.views.SmartSwitchPreference
            android:key="dhcp_setting_key"
            android:title="@string/dhcp_setting" />

        <Preference
            android:key="port_mapping_key"
            android:title="@string/port_mapping" >
        </Preference>
    </PreferenceCategory>

</PreferenceScreen>

刚开始dhcp_setting_key我定义的是SwitchPreference 但是问题来了,点击preference跳转到新设置界面的时候,同时也会对Switch进行操作。仔细分析源码后发现在SwitchPreference 父类onClick方法中对switch进行了操作,那么解决这个问题就简单了,重写一个 继承SwitchPreference的类,将onClick方法重写就可以了(因为我不需要操作,直接为空就好)。下面贴上代码!

package com.android.settings.ethernet.views;

import android.content.Context;
import android.os.SystemProperties;
import android.preference.SwitchPreference;
import android.text.TextUtils;
import android.util.AttributeSet;
import com.android.settings.R;
import android.view.View.OnClickListener;
import android.view.View;
import android.view.ViewGroup;
import android.widget.Switch;
import android.widget.CompoundButton.OnCheckedChangeListener;
import android.widget.CompoundButton;

public class SmartSwitchPreference extends SwitchPreference {
    public Switch mDhcpSwitch ;
    private String mEthernetStatus;
   private boolean mChecked;

    public SmartSwitchPreference(Context context, AttributeSet attrs, int defStyle) {
        super(context, attrs, defStyle);
        setWidgetLayoutResource(R.layout.ethernet_preference_header_switch_item);
        
    }

    public SmartSwitchPreference(Context context, AttributeSet attrs) {
        super(context, attrs);
        setWidgetLayoutResource(R.layout.ethernet_preference_header_switch_item);
    }

    public SmartSwitchPreference(Context context) {
        super(context);
        setWidgetLayoutResource(R.layout.ethernet_preference_header_switch_item);
    }

    
    @Override
    public void onBindView(View view) {
        super.onBindView(view);
         mDhcpSwitch = (Switch) view.findViewById(R.id.ethernet_switchWidget);
         initSwitch();
         mDhcpSwitch.setOnClickListener(new OnClickListener() {
            
            @Override
            public void onClick(View v) {
                if (mDhcpSwitch.isChecked()) {
                    SystemProperties.set("sys.dnsmasq.status", "on");
                } else {
                    SystemProperties.set("sys.dnsmasq.status", "off");
                }
            }
        });
         
         mDhcpSwitch.setChecked(mChecked);
    }
    
    
    @Override
    protected View onCreateView(ViewGroup parent) {
        // TODO Auto-generated method stub
        return super.onCreateView(parent);
    }
    
    @Override
    public void setChecked(boolean checked) {
        mChecked = checked;
        notifyChanged(); 
    }
    
    @Override
    public boolean isChecked() {
        // TODO Auto-generated method stub
        return super.isChecked();
    }
    
    public void initSwitch() {
        mEthernetStatus = SystemProperties.get("sys.dnsmasq.status", null);
        if (TextUtils.isEmpty(mEthernetStatus) || mEthernetStatus == null) {
            mDhcpSwitch.setChecked(false);
        } else if ("on".equals(mEthernetStatus)) {
            mDhcpSwitch.setChecked(true);
        } else if ("off".equals(mEthernetStatus)) {
            mDhcpSwitch.setChecked(false);
        } else {
            mDhcpSwitch.setChecked(false);
        }
    }
    
    @Override
    protected void onClick() {
        
    }
}
如果不需要对preferce进行点击操作,那么直接在xml文件里添加android:selectable="false"是最方便的操作了。

下面的是ethernet_preference_header_switch_item.xml文件

<?xml version="1.0" encoding="utf-8"?>

<!-- Layout of a ethernet item in PreferenceActivity. -->
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
    android:layout_width="match_parent"
    android:layout_height="wrap_content"
    android:minHeight="48dp"
    android:background="?android:attr/activatedBackgroundIndicator"
    android:gravity="center_vertical"
    android:paddingEnd="?android:attr/scrollbarSize">

    <RelativeLayout
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:layout_marginStart="2dip"
        android:layout_marginEnd="6dip"
        android:layout_marginTop="6dip"
        android:layout_marginBottom="6dip"
        android:layout_weight="1">

        <TextView android:id="@+android:id/ethernet_title"
            android:layout_width="wrap_content"
            android:layout_height="wrap_content"
            android:singleLine="true"
            android:textAppearance="?android:attr/textAppearanceMedium"
            android:ellipsize="marquee"
            android:fadingEdge="horizontal" />


    </RelativeLayout>

   <Switch android:id="@+id/ethernet_switchWidget"
       android:layout_width="wrap_content"
       android:layout_height="wrap_content"
       android:layout_gravity="center"
       android:padding="8dip"
       android:focusable="false"
       android:clickable="true" />

</LinearLayout>


应用延伸:

我在使用的过程中发现EthernetSettings类中无法对switchPreferce里面的switch控件进行操作,一直报空指针。明明有对switchPreferce进行初始化啊,为什么不能对switch进行操作呢?这个问题困扰了我整整一上午,最后锁定到了onBindView(View view)方法中,通过别人的解释终于明白了。下面贴上别人分析的流程,^_^~

>public class PowerInformationPreference extends Preference {  
  
    private TextView firstName;  
  
    public PowerInformationPreference(Context context, AttributeSet attr, int defStyle) {  
        super(context, attr, defStyle);  
        setLayoutResource(R.layout.layout_power_information);  
    }  
  
    @Override  
    protected void onBindView(View view) {(1)  
        // TODO Auto-generated method stub  
        super.onBindView(view);  
        firstName = (TextView)view.findViewById(R.id.firstname);(2)  
    }  
  
} 

上面是我们很常见到的自定义preference的基本形式,我们在onBindView()函数中操作自定义视图,比如上面的代码中我们需要动态的改变firstName这一TextView的text值。

如下,我们提供一个setText()接口,通过这个接口我们可以动态改变TextView的text值。

public void setText(String text){(3)  
    firstName.setText(text);//NullPointerException(4)  
} 
如果我们真的是这样写这个接口的话,会发现一定会出现NullPointerException,调试发现这里的firstName为空。而回到onBindView()中调试分析又会发现,firstName = (TextView)view.find...实际上是会运行的,也就是有这个firstName对象存在。那么唯一的解释就是setText()的调用在onBindView()运行之前,因此我们的疑惑来了,如何确保onBindView()在setText()调用之前运行呢?

事实上,到这里我们如果继续这样分析的话,就会钻入误区,跳出来,从逻辑层面上想想,我们会发现,(1),(2),(4)是对UI的操作,(3)是对数据的操作。这里,(1)(2)和(4)分开了,也就是说,(1)(2)运行的时候不能保证(4)运行;(4)运行的时候不能保证(1)(2)运行,这对UI操作是非常危险的事,很容易就产生了控制正异常。

不仅仅是这里,我们查看Adapter.java的源码,查看有getView(), onCreateView(), onBindView()等类的相关源码,都会发现,凡是涉及到UI操作的一定是放在一起的。

<pre code_snippet_id="159439" snippet_file_name="blog_20140116_3_2925943" name="code" class="java"><span style="font-size:14px;">public class PowerInformationPreference extends Preference {  
  
    private TextView firstName;  
    private String text;  
  
    public PowerInformationPreference(Context context, AttributeSet attr, int defStyle) {  
        super(context, attr, defStyle);  
        setLayoutResource(R.layout.layout_power_information);  
    }  
  
    @Override  
    protected void onBindView(View view) {(1)  
        // TODO Auto-generated method stub  
        super.onBindView(view);  
        firstName = (TextView)view.findViewById(R.id.firstname);(2)  
        firstName.setText(text);  
    }  
</span></pre>  
<br>  
public void setText(String text){(3) this.text = text;<br>  
notifyChanged();<br>  
}<br>  
}<br>  
<p></p>  
<pre></pre>  
这里还需要强调的一点是一定不要忘记notifyChanged()这个函数通知UI更新数据,否则我们会发现通过这个接口设置的数据并没有改变原来的值。这个函数如同View中的invalidate(),虽然数据更新了,但是显示在视图中的数字图像依然没变,因此需要通知UI更新显示。<br>  
<p></p> 

总结下吧,switchpreferce 和普通的View 加载layout的方式不同。

 switchpreferce :setWidgetLayoutResource(R.layout.ethernet_preference_header_switch_item); 我们无法获取VIew,所以对view的操作,只能通过onBindView(View view)来操作。 

View: View view = LayoutInflater.from(context).inflate(R.layout.ipedittext, this); 普通view,我们可以在构造函数中直接得到view,从而在构造函数中直接可以初始化控件。




评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值