自定义菜单收起展开动画(二)

来源:互联网 发布:libcurl https linux 编辑:程序博客网 时间:2024/06/04 20:04

今天周一,新的一周,水的开始。

上次发完博客后,自己在项目中使用不是特别的方便,除了要写很多布局之外,还要写诸多的点击事件的监听,不是非常的方便,于是花了一天的时间封装了一下。

首先我们先缕缕思路,首先我想实现一个自定义的控件可以实现全部的父子菜单的功能,所以在原有的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中全部的代码,并没有什么非常有难度的,在我们的封装下变得十分的简单并且清晰,即使你以后甩锅不干之后,也能造福下一个程序汪,是不是很好呢?汪汪汪!最后附上效果图一张:


















1 0
原创粉丝点击