I gave this one a try and struggled a bit. I thought I would share my experience.
At first I tried the answer by XGouchet since it had the most up votes. The solution was fairly complicated and required using preference headers which are very cool but did not fit what I was doing. I decided the easiest thing for me to do is to wrap up my preferenceFragment in a regular fragment and make fake the switch preference with a regular view. I dug up the android source for a preference and came up with this
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:tools="http://schemas.android.com/tools"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:orientation="vertical"
tools:context="com.lezyne.link.ui.homeScreen.settingsTab.SettingsFragment">
<FrameLayout
android:id="@+id/fragment_container"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_gravity="center_horizontal">
</FrameLayout>
<View
android:layout_width="fill_parent"
android:layout_height="1dp"
android:layout_marginLeft="15dp"
android:layout_marginRight="15dp"
android:background="#e1e1e1" />
<LinearLayout
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:background="?android:attr/selectableItemBackground"
android:gravity="center_vertical"
android:minHeight="?android:attr/listPreferredItemHeight"
android:orientation="horizontal"
android:paddingRight="?android:attr/scrollbarSize">
<ImageView
android:id="@+id/icon"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_gravity="center" />
<RelativeLayout
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_marginBottom="6dip"
android:layout_marginLeft="15dip"
android:layout_marginRight="6dip"
android:layout_marginTop="6dip"
android:layout_weight="1">
<TextView
android:id="@+id/title"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:ellipsize="marquee"
android:fadingEdge="horizontal"
android:singleLine="true"
android:textAppearance="?android:attr/textAppearanceLarge" />
<TextView
android:id="@+id/summary"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_alignLeft="@android:id/title"
android:layout_below="@android:id/title"
android:maxLines="4"
android:textAppearance="?android:attr/textAppearanceSmall"
android:textColor="?android:attr/textColorSecondary" />
</RelativeLayout>
<!-- Preference should place its actual preference widget here. -->
<RelativeLayout
android:id="@+id/widget_frame"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:gravity="center_vertical"
android:orientation="vertical"
android:paddingEnd="36dp">
<Switch
android:thumb="@drawable/switch_inner"
android:id="@+id/switch1"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_alignParentEnd="true" />
<TextView
android:id="@+id/textView6"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_marginLeft="2dp"
android:text="@string/Notifications"
android:textAppearance="?android:attr/textAppearanceMedium" />
</RelativeLayout>
</LinearLayout>
<View
android:layout_width="fill_parent"
android:layout_height="1dp"
android:layout_marginLeft="15dp"
android:layout_marginRight="15dp"
android:background="#e1e1e1" />
</LinearLayout>
The FrameLayout at the top gets a preference fragment like so
getChildFragmentManager()
.beginTransaction()
.replace(R.id.fragment_container, new SettingsPreferencesFragment(), "SettingsPreferencesFragment")
.commit();
Then I can get assign my click listeners as I would in any regular view. SettingsPreferencesFragment is just totally standard preference fragment.
This solution seemed pretty good, but then I noticed weird layout issues on tablets. I realized that I would have trouble getting this solution to look right on all devices and I needed to use a real switchPreference not a fake one.
============== Solution 2 ===============
AlikElzin-kilaka's solution was nice and simple but did not work when I tried. I tried a 2nd time to make sure that I had not just done it wrong. I kept playing around and came up with something that seems to work. He has a good point about
2 questions here: 1. How to listen to preference click outside the
switch area? 2. How to put a switch in the actionbar?
Really only question (1) is worth answering because question 2 has been answered here and other places
I realized the only way to get access to the views in a preference was to create a child class and override onBind. So I came up with this child class of SwitchPreference that creates separate click handlers for the switch as the entire view. Its still a hack though.
public class MySwitchPreference extends SwitchPreference {
public MySwitchPreference(Context context, AttributeSet attrs, int defStyleAttr) {
super(context, attrs, defStyleAttr);
}
public MySwitchPreference(Context context, AttributeSet attrs) {
super(context, attrs);
getView(null,null);
}
public MySwitchPreference(Context context) {
super(context);
}
public interface SwitchClickListener{
public void onSwitchClicked(boolean checked);
public void onPreferenceClicked();
}
private SwitchClickListener listener = null;
public void setSwitchClickListener(SwitchClickListener listener){
this.listener = listener;
}
public Switch findSwitchWidget(View view){
if (view instanceof Switch){
return (Switch)view;
}
if (view instanceof ViewGroup){
ViewGroup viewGroup = (ViewGroup)view;
for (int i = 0; i < viewGroup.getChildCount();i++){
View child = viewGroup.getChildAt(i);
if (child instanceof ViewGroup){
Switch result = findSwitchWidget(child);
if (result!=null) return result;
}
if (child instanceof Switch){
return (Switch)child;
}
}
}
return null;
}
protected void onBindView (View view){
super.onBindView(view);
final Switch switchView = findSwitchWidget(view);
if (switchView!=null){
switchView.setOnClickListener(new View.OnClickListener() {
@Override
public void onClick(View v) {
if (listener!=null) listener.onSwitchClicked(switchView.isChecked());
}
});
switchView.setFocusable(true);
switchView.setEnabled(true);
}
view.setOnClickListener(new View.OnClickListener() {
@Override
public void onClick(View v) {
if (listener!=null) listener.onPreferenceClicked();
}
});
}
}
It uses the recursive function findSwitchWidget to walk the tree until it finds a Switch. I would have rather write code like this:
Switch switchView = view.findViewById(android.R.id.switchView);
but there does not seem to be a way to get to that internal id value that I know of. Anyhow once we have the actual switch we can assign listeners to it and the container view. The switch preference won't update automatically so its necessary to save the preference yourself.
MySwitchPreference switchPreference = (MySwitchPreference) findPreference("whatever");
switchPreference.setSwitchClickListener(new MySwitchPreference.SwitchClickListener() {
@Override
public void onSwitchClicked(boolean checked) {
//Save the preference value here
}
@Override
public void onPreferenceClicked() {
//Launch the new preference screen or activity here
}
});
Hopefully this 2nd hack wont come back to bite me.
Any one see any potential pitfalls of this method?
I also put a slightly improved version of the code on github https://gist.github.com/marchold/45e22839eb94aa14dfb5