今天周一,新的一周,水的开始。
上次发完博客后,自己在项目中使用不是特别的方便,除了要写很多布局之外,还要写诸多的点击事件的监听,不是非常的方便,于是花了一天的时间封装了一下。
首先我们先缕缕思路,首先我想实现一个自定义的控件可以实现全部的父子菜单的功能,所以在原有的adapter基础上,我们需要增加累死expanedlistview类似的adapter的实现。
public abstract class SettingsAdapter<T> {
private Context context;
private List<T> mParentList;
private List<List<T>> mChildList;
public SettingsAdapter(Context context, List<T> mParentList, List<List<T>> mChildList) {
this.context = context;
this.mParentList = mParentList;
this.mChildList = mChildList;
}
public int getParentCount(){
return mParentList==null?0:mParentList.size();
}
public T getItem(int position){
return mParentList.get(position);
}
public int getChildCount(int parentPosition){
return mChildList==null?0: mChildList.get(parentPosition).size();
}
public T getChildItem(int parentPosition,int childPosition){
return mChildList.get(parentPosition).get(childPosition);
}
public abstract View getParentView(View view,int position);
public abstract View getChildView(View view,int parentPosition,int childPosition);
public interface onDataChanged {
void changed();
}
public void setOnDataChanged(onDataChanged onDataChanged) {
this.onDataChanged = onDataChanged;
}
public onDataChanged onDataChanged;
public void notifyDataSetChanged(){
onDataChanged.changed();
}
}
这次的adapter和上次的adapter大致上是一样的,不过是多了父菜单的view,以便于我们对于父布局的定制。
然后我们在原来的SettingView上进行修改,这次我们因为要实现的是一整个的菜单,所以依然是继承linearlayout,在初始化中设置方向为竖向。
public SettingsView(Context context) {
this(context, null);
}
public SettingsView(Context context, AttributeSet attrs) {
this(context, attrs, 0);
}
public SettingsView(Context context, AttributeSet attrs, int defStyleAttr) {
super(context, attrs, defStyleAttr);
this.context=context;
setOrientation(VERTICAL);
}
然后我们在SettingView中实现setAdapter这个接口,和上个博客的写法大致一样。
public void setAdapter(SettingsAdapter adapter){
this.adapter=adapter;
changeAdapter();
}
下面我们重点看一下changAdapter里面的方法。
private void changeAdapter() {
removeAllViews();
SettingsAdapter settingsAdapter=this.adapter;
for(int i=0;i<settingsAdapter.getParentCount();i++){
final View parentView = settingsAdapter.getParentView(this, i);
parentView.setTag(i);
addView(parentView);
parentView.setOnClickListener(new OnClickListener() {
@Override
public void onClick(View v) {
if(onItemClickListener!=null){
onItemClickListener.ParentClicked((Integer) parentView.getTag());
}
}
});
innerLayout=new LinearLayout(context);
LinearLayout.LayoutParams lp=new LinearLayout.LayoutParams(ViewGroup.LayoutParams.MATCH_PARENT, ViewGroup.LayoutParams.MATCH_PARENT);
innerLayout.setLayoutParams(lp);
innerLayout.setOrientation(LinearLayout.VERTICAL);
for(int j=0;j<settingsAdapter.getChildCount(i);j++){
final View childView = settingsAdapter.getChildView(this, i, j);
childView.setTag(String.valueOf(i) + String.valueOf(j));
childView.setOnClickListener(new OnClickListener() {
@Override
public void onClick(View v) {
if(onItemClickListener!=null){
String tag= (String) childView.getTag();
onItemClickListener.childClicked(TagToInt(tag,0,1),TagToInt(tag,1,2));
}
}
});
innerLayout.addView(childView);
}
addView(innerLayout);
}
}
首先,我们先通过removeAllViews来清空布局,以免发生什么可怕的事情,然后通过循环adapter中的getparentCount方法来加载总共有几项父菜单,然后我们通过innerLinearLayout来add子菜单的布局,方便接下来我们对子菜单的高度的计算。然后我们通过对parentView和childView设置tag来实现点击事件,其中的tagToInt的方法在这里。
private int TagToInt(String str,int start,int end){
String substring = str.substring(start, end);
int i= Integer.parseInt(substring);
return i;
}
我们在adapter中通过接口onDataChanged 实现了notify的功能,所以我们需要在SettingView中对它进行实现,所以在settingView中实现该接口,并完成回调。 @Override
public void changed() {
changeAdapter();
}
以及我们对click事件的回调。
public OnItemClickListener onItemClickListener;
public void setOnItemClickListener(OnItemClickListener onItemClickListener) {
this.onItemClickListener = onItemClickListener;
}
public interface OnItemClickListener{
void ParentClicked(int position);
void childClicked(int parentPosition,int childPosition);
}
这样的话我们的控件已经有点样子了,但是不满足是程序员进步的最大动力,所以我继续封装了对于控件高度的计算。
public List<View> getChildLayout(){
List<View> list=new ArrayList<>();
for(int i=1;i<getChildCount();i+=2){
View childView = getChildAt(i);
list.add(childView);
}
return list;
}
这里的代码会有点生涩,首先我们通过循环子控件的个数,第0个是父菜单的parentView,第1个才是我们所需要的子菜单的innerlayout,所以我们从1开始循环,i+=2,也就非常容易的就懂了,因为我们的子菜单的view是每隔一行parentView的,所以需要i+=2。这样我们就得了子菜单的所有view集合。接下来我们就开始计算高度,上文中,我们提到了在activity中和fragment中计算高度的方法是不一样的,所以我们在这里定义一个boolean值来判断是否在activity中。
public boolean isActivity=true;
然后在activity中通过post方法来获取子菜单View的高度。
this.post(new Runnable() {
@Override
public void run() {
List<View> childLayout = getChildLayout();
heightList.clear();
for(int i=0;i<childLayout.size();i++){
int height = childLayout.get(i).getHeight();
heightList.add(height);
}
}
});
然后再fragment中就稍微复杂一些,首先我们要实现 ViewTreeObserver.OnGlobalLayoutListener这个监听来监听控件的高度。
ViewTreeObserver.OnGlobalLayoutListener listener=new ViewTreeObserver.OnGlobalLayoutListener() {
@Override
public void onGlobalLayout() {
List<View> childLayout = getChildLayout();
heightList.clear();
for(int i=0;i<childLayout.size();i++){
int height = childLayout.get(i).getHeight();
heightList.add(height);
}
}
};
这样我们不管在activity中还是fragment中都获取了控件的高度,于是我们继续封装出来一个方法来判断是否在activity中。 public void initHeight(boolean isActivity){
this.isActivity=isActivity;
if(isActivity){
this.post(new Runnable() {
@Override
public void run() {
List<View> childLayout = getChildLayout();
heightList.clear();
for(int i=0;i<childLayout.size();i++){
int height = childLayout.get(i).getHeight();
heightList.add(height);
}
}
});
}else{
this.getViewTreeObserver().addOnGlobalLayoutListener(listener);
}
}
于是我们在每次调用的时候只需要在findview后面initHeight方法来获取子菜单view的高度集合了。值得注意的是因为ViewTreeObserver.OnGlobalLayoutListener是适时监听的,所以我们需要在首次点击之后动画效果生成之前移除他的监听。 public ViewTreeObserver.OnGlobalLayoutListener getListener(){
return listener;
}
//移除监听
public void removeOnGlobalLayoutListener(View v, ViewTreeObserver.OnGlobalLayoutListener listener) {
if (Build.VERSION.SDK_INT < 16) {
v.getViewTreeObserver().removeGlobalOnLayoutListener(listener);
} else {
v.getViewTreeObserver().removeOnGlobalLayoutListener(listener);
}
}
这样我们就基本上完成了对控件的封装,最后我们在对上篇博客中的动画效果做最后一层的封装。<pre name="code" class="java"> public void animateBegin(int position){
if (getChildLayout().get(position).getVisibility() == View.VISIBLE) {
if(!isActivity){
removeOnGlobalLayoutListener(getChildView(position), getListener());
}
AnimUtils.animatorClose(getChildLayout().get(position), getChildItemHeight(position));
} else {
AnimUtils.animatorOpen(getChildLayout().get(position), getChildItemHeight(position));
}
}
这里的封装也十分简单,首先就是判断是否在fragment中,如果是fragment我们就移除掉对于控件高度的监听,是不是非常的通俗易懂呢?最后我们来实践一下。
<pre name="code" class="html"><RelativeLayout 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">
<ScrollView
android:layout_width="match_parent"
android:layout_height="wrap_content">
<LinearLayout
android:layout_width="match_parent"
android:layout_height="match_parent"
android:orientation="vertical"
>
<com.example.mydemo2.SettingsView
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:id="@+id/sv"
></com.example.mydemo2.SettingsView>
<Button
android:layout_width="match_parent"
android:layout_height="45dp"
android:id="@+id/change_fragment"
android:text="切换到fragment"
/>
<FrameLayout
android:layout_width="match_parent"
android:layout_height="500dp"
android:id="@+id/fragment_layout"
></FrameLayout>
</LinearLayout>
</ScrollView>
</RelativeLayout>
这是MainActivity的布局文件,我们点击button之后会调用repalce fragment的方法,来显示fragment中的settingview。
sv= (SettingsView) findViewById(R.id.sv);
btn= (Button) findViewById(R.id.change_fragment);
sv.initHeight(true);
for(int i=0;i<3;i++){
mList.add(i+"这是父菜单");
}
for(int i=0;i<3;i++){
List<String> list=new ArrayList<>();
for(int j=0;j<5;j++){
list.add("这是第"+i+"父菜单,第+"+j+"子菜单");
}
mList2.add(list);
}
SettingsAdapter adapter=new SettingsAdapter(MainActivity.this,mList,mList2) {
@Override
public View getParentView(View view, int position) {
View view1 = LayoutInflater.from(MainActivity.this).inflate(R.layout.item_a, null);
TextView tv= (TextView) view1.findViewById(R.id.tv1);
tv.setText(mList.get(position));
return view1;
}
@Override
public View getChildView(View view, int parentPosition, int childPosition) {
View view2 = LayoutInflater.from(MainActivity.this).inflate(R.layout.item_b, null);
TextView tv= (TextView) view2.findViewById(R.id.tv2);
tv.setText(mList2.get(parentPosition).get(childPosition));
return view2;
}
};
sv.setAdapter(adapter);
sv.setOnItemClickListener(new SettingsView.OnItemClickListener() {
@Override
public void ParentClicked(int position) {
sv.animateBegin(position);
}
@Override
public void childClicked(int parentPosition, int childPosition) {
Toast.makeText(MainActivity.this, mList2.get(parentPosition).get(childPosition), Toast.LENGTH_SHORT).show();
}
});
btn.setOnClickListener(new View.OnClickListener() {
@Override
public void onClick(View v) {
FragmentTransaction ft = getSupportFragmentManager().beginTransaction();
TestFragment fragment=new TestFragment();
ft.replace(R.id.fragment_layout,fragment);
ft.commit();
}
});
}
这是MainActivity中全部的代码,并没有什么非常有难度的,在我们的封装下变得十分的简单并且清晰,即使你以后甩锅不干之后,也能造福下一个程序汪,是不是很好呢?汪汪汪!最后附上效果图一张: