最近需求sama联合美工娘娘又改了界面,整体的界面是华丽了不少,但是大神何必为难弱智儿童的我呢,下面先看看新界面~
很经典的菜单设计,不过毕竟是版本更迭,不适合在原有基础上大修改改,菜单总共分了4个父菜单和若干个子菜单,点击父菜单会隐藏子菜单,其中还要有收起展开动画,这个首先令我想起了expanedlistview。说动手就动手,花了几分钟先写个demo测试以下吧。
<?xml version="1.0" encoding="utf-8"?> <RelativeLayout xmlns:android="http://schemas.android.com/apk/res/android" android:layout_width="match_parent" android:layout_height="match_parent" > <ExpandableListView android:layout_width="match_parent" android:layout_height="match_parent" android:id="@+id/exlistview" ></ExpandableListView> </RelativeLayout>
这个很简单的布局代码,不必多说了~
然后定义一个实体类,包含父子菜单所必需的内容
public class Bean { public Bean(String title, List<childBean> childBeans) { this.title = title; this.childBeans = childBeans; } public Bean() { } public String getTitle() { return title; } public void setTitle(String title) { this.title = title; } public List<childBean> getChildBeans() { return childBeans; } public void setChildBeans(List<childBean> childBeans) { this.childBeans = childBeans; } private String title; private List<childBean> childBeans; public static class childBean{ private String title; public childBean(String title) { this.title = title; } public String getTitle() { return title; } public void setTitle(String title) { this.title = title; } } }
这几行代码也不必多说,一目了然,如果需要添加其他的 自然也可以自己添加~
最后开始写自定义adapter了,也很简单不是吗?
public class TestAdapter extends BaseExpandableListAdapter { private List<Bean> groups; private Context context; public TestAdapter( Context context, List<Bean> groups){ this.context=context; this.groups=groups; } @Override public int getGroupCount() { return groups.size(); } @Override public int getChildrenCount(int groupPosition) { return groups.get(groupPosition).getChildBeans().size(); } @Override public Object getGroup(int groupPosition) { return groups.get(groupPosition); } @Override public Object getChild(int groupPosition, int childPosition) { return groups.get(groupPosition).getChildBeans().get(childPosition); } @Override public long getGroupId(int groupPosition) { return groupPosition; } @Override public long getChildId(int groupPosition, int childPosition) { return childPosition; } @Override public boolean hasStableIds() { return false; } @Override public View getGroupView(int groupPosition, boolean isExpanded, View convertView, ViewGroup parent) { View view= LayoutInflater.from(context).inflate(R.layout.item_a,null); TextView tv= (TextView) view.findViewById(R.id.a_text); tv.setText(groups.get(groupPosition).getTitle()); return view; } @Override public View getChildView(int groupPosition, int childPosition, boolean isLastChild, View convertView, ViewGroup parent) { View view= LayoutInflater.from(context).inflate(R.layout.item_a,null); TextView tv= (TextView) view.findViewById(R.id.a_text); tv.setText(groups.get(groupPosition).getChildBeans().get(childPosition).getTitle()); return view; } @Override public boolean isChildSelectable(int groupPosition, int childPosition) { return false; } }
很简单的代码,毕竟只是demo,大家就将就一下看看,最后在activity里调用一下,我们来看效果吧~
public class MainActivity extends AppCompatActivity { private ExpandableListView listView; private List<Bean> mList=new ArrayList<>(); private List<Bean.childBean> childList=new ArrayList<>(); @Override protected void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); setContentView(R.layout.activity_main); listView= (ExpandableListView) findViewById(R.id.exlistview); Bean.childBean childBean; for(int j=0;j<3;j++){ childBean=new Bean.childBean("aaaa"+j); childList.add(childBean); } for(int i=0;i<3;i++){ mList.add(new Bean(i+"",childList)); } TestAdapter adapter=new TestAdapter(MainActivity.this,mList); listView.setAdapter(adapter); listView.setGroupIndicator(null); } }
最后 上动图看效果:
太粗糙了,而且没有收起展开的动画,没办法,继续百度吧,然后找到了一个叫animationExpanedListView的东东,但是我感觉比较久远了,上次更新已经2年之前了,有想实验的小伙伴可以去这里---->https://github.com/idunnololz/AnimatedExpandableListView。
怎么办呢,看来只有自己想办法了。首先屡清楚思路,模仿listview的效果可以试试,所以首先写一个自定义的adapter出来~
public abstract class SettingsAdapter<T> { private Context context; private List<T> mList; public SettingsAdapter(Context context, List<T> mList){ this.context=context; this.mList=mList; } public SettingsAdapter(T[] mDatas){ mList=new ArrayList<T>(Arrays.asList(mDatas)); } public int getCount(){ return mList==null?0:mList.size(); } public T getItem(int position){ return mList.get(position); } public void notifyDataSetChanged(){ onDataChanged.changed(); } public abstract View getView(SettingView parent, int position); public interface onDataChanged { void changed(); } public void setOnDataChanged(onDataChanged onDataChanged) { this.onDataChanged = onDataChanged; } public onDataChanged onDataChanged; }
这一段代码很容易理解,跟普通的adapter没有啥不一样的~继续向下我们要实现子菜单的布局,首先我们继承linearlayout,因为菜单毕竟是自上向下的嘛~
public class SettingView extends LinearLayout implements SettingsAdapter.onDataChanged{ private Context context; private SettingsAdapter adapter; public SettingView(Context context) { this(context, null); } public SettingView(Context context, AttributeSet attrs) { this(context, attrs, 0); } public SettingView(Context context, AttributeSet attrs, int defStyleAttr) { super(context, attrs, defStyleAttr); this.context=context; setOrientation(VERTICAL); }
接下来我们公开一个adapter调用的方法用法调用adapter;
public void setAdapter(SettingsAdapter adapter){ this.adapter=adapter; changeAdapter(); }
这依然是很简单的代码。我们接下来看changeAdapter里面的内容
private void changeAdapter() { removeAllViews(); SettingsAdapter settingsAdapter = this.adapter; for(int i=0;i<settingsAdapter.getCount();i++){ final View layout = settingsAdapter.getView(this, i); layout.setTag(i); addView(layout); layout.setOnClickListener(new OnClickListener() { @Override public void onClick(View v) { if(onItemClick!=null){ onItemClick.click(v,(Integer) layout.getTag()); } } }); } }
一目了然是不是,如果看不懂没关系,那就多看几遍,如果多看几遍还是看不懂,也没关系,其实我也不懂。
最后我们通过接口回调将接口公布出去,这是我们的接口以及实现的adapter中接口:
public interface OnItemClick { void click(View v,int i); } public void setOnItemClick(OnItemClick onItemClick) { this.onItemClick = onItemClick; } private OnItemClick onItemClick; @Override public void changed() { changeAdapter(); }
最后在xml中的布局就是这样子的:
<?xml version="1.0" encoding="utf-8"?> <LinearLayout xmlns:android="http://schemas.android.com/apk/res/android" android:layout_width="match_parent" android:layout_height="match_parent" android:orientation="vertical" > <!-- <ExpandableListView android:layout_width="match_parent" android:layout_height="match_parent" android:id="@+id/exlistview" ></ExpandableListView>--> <TextView android:layout_width="match_parent" android:layout_height="50dp" android:background="@android:color/holo_red_dark" android:text="1111111111111111111" android:textSize="20sp" android:gravity="center" android:id="@+id/tv1" /> <com.example.mydemo.SettingView android:layout_width="match_parent" android:layout_height="wrap_content" android:id="@+id/test_layout" /> </LinearLayout>
Textview暂时充当的是父菜单,settingview就是子菜单咯~
tv= (TextView) findViewById(R.id.tv1); sv= (SettingView) findViewById(R.id.test_layout); for(int i=0;i<5;i++){ mList.add(i+""); } SettingsAdapter adapter=new SettingsAdapter(MainActivity.this,mList) { @Override public View getView(SettingView parent, int position) { View view=getLayoutInflater().inflate(R.layout.item_a,null); TextView tv= (TextView) view.findViewById(R.id.a_text); tv.setText(mList.get(position)); return view; } }; sv.setAdapter(adapter); sv.setOnItemClick(new SettingView.OnItemClick() { @Override public void click(View v,int i) { Toast.makeText(MainActivity.this,mList.get(i),Toast.LENGTH_SHORT).show(); } }); }
最后在activity里敲这些很简单的代码,差不多第一步的自定义布局就算实现了。看效果
布局是写好了,但是暂时还没有任何的点击效果和动画效果,这时候想到了什么呢,属性动画,当当当~不过objectAnimator里并没有对View高度的动画,不过我们知道valueAnimator是可以实现这个的,不多说看代码~
public static ValueAnimator DropAnim(final View view,int start,int end){ ValueAnimator animator=ValueAnimator.ofInt(start,end); animator.addUpdateListener(new ValueAnimator.AnimatorUpdateListener() { @Override public void onAnimationUpdate(ValueAnimator animation) { int value= (Integer) animation.getAnimatedValue(); ViewGroup.LayoutParams lp = view.getLayoutParams(); lp.height=value; view.setLayoutParams(lp); } }); return animator; }
首先,我们需要把target的view 和view的起始高度传起来,最后通过动画的listener实现对view高度的动态变化,看到这里是不是有些思路了~~~
public static void animatorOpen(final View view, int height){ view.setVisibility(View.VISIBLE); AnimatorSet set=new AnimatorSet(); set.setDuration(1000); ValueAnimator animator = DropAnim(view, 0, height); ObjectAnimator oa=ObjectAnimator.ofFloat(view, View.ALPHA, 0.0f, 1.0f); set.playTogether(animator,oa); set.setDuration(1000); set.start(); } public static void animatorClose(final View view, int height){ AnimatorSet set=new AnimatorSet(); set.setDuration(1000); ValueAnimator animator = DropAnim(view, height,0); ObjectAnimator oa=ObjectAnimator.ofFloat(view,View.ALPHA,1.0f,0.0f); set.playTogether(animator,oa); set.addListener(new AnimatorListenerAdapter() { @Override public void onAnimationEnd(Animator animation) { super.onAnimationEnd(animation); view.setVisibility(View.GONE); } }); set.start(); }
这是动画开关的两个方法,方法大致相同,我们不仅实现了高度的变化,而且加入了透明度的变化,当然你也可以加入一些其他的动画,这里就不多做介绍了。
最后在在点击事件中通过判断子菜单的vissable来判断动画的开合,是不是很简单呢?最后的一个难点是获取view的高度,我们并没有在自定义的布局里直接获取到他的高度,当然你也可以在自定义view的时候直接通过 getMeasuredHeight()
的方法获得出来,当然也可以通过post方法进行获取
sv.post(new Runnable() { @Override public void run() { height=sv.getHeight(); } }); tv.setOnClickListener(new View.OnClickListener() { @Override public void onClick(View v) { if(sv.getVisibility()==View.VISIBLE){ AnimUtils.animatorClose(sv,height); }else{ AnimUtils.animatorOpen(sv,height); } } });
最后大功告成~来看一下动画效果吧~
是不是很炫呢?最后有一个小问题,就是如何在fragment中获取控件的高度呢,起初我也是傻傻的post方法,然并卵的是然并卵啊~最后还是找到了答案~通过
ViewTreeObserver来获取,然后在点击或者初始化完成之后进行移除监听,不然他会一直监听控件的高度,切记,移除这个监听,必须放在动画之前,否则会出现意想不到的彩蛋呢~
好了,第一篇博客大功告成,我要去水群了~~~~大家再见·~~~~~