仿SegmentFault系列(一) PopupMenu
前言
一直觉得SegmentFault安卓客户端做的很不错,特别棒的MetrialDesign风格实践,当然包括知乎我也很喜欢,不过有一天我不小心反编出了SegmentFault的api接口,所以我决定亲自实现一遍SegmentFault安卓客户端。希望能通过这一系列的博客将其中的技术要点记录下来,跟大家一起分享。网络上很容易找到的资料这里就不再赘述,重点是分大家分享一下瞎研究的奇技淫巧和黑科技。
这次选择了PopupMenu作为第一篇,多少还是因为太复杂的东西怕自己hold不住,水平有限,希望能给大家带来一个清晰的开篇。
先走一波效果图
基本上是还原了SegmentFault中分享菜单的设计,只是SegmentFault种菜单的selector并没有使用ripple效果,不过抛开这个,其他地方可以说是一模一样了。
技术选型
如果说到分享的话,最直觉的做法就是使用ShareActionProvider了,于是网上找了一波资料后发现做出来是这样的??
这个问题不小啊… 首先菜单位置并不是顶到最上面的啊??而且我这个分享图标怎么蜜汁变大了呢??而且我怎么控制菜单里显示的东西呢??人家上来显示微博分享、微信分享、各种分享的,我这怎么成了小米快传了??
然后我又点开了查看全部
这又是什么情况?? 是我身体不行了看东西有重影了??看起来像是新的菜单没有完全遮挡住旧的菜单,然后出现重叠了
接着我不小心手抖点了下ForkHub,等我退回来的时候就酱了。。
对没错我的toolbar上乱入了一直喵咪,应该是谷歌贴心的认为这是你最常用的分享应用,所以就给你显示在这里了,可是我一句话都没说好吗?!
心灰意冷,所以最终决定使用popupMenu来实现动图中所出现的效果。所以只要我们能够拿到系统中支持分享的应用列表,让popupMenu显示出来就可以了。
好了我要开始分析源码了!
为了实现最终效果,还真得好好看一遍PopupMenu的实现方式,我就假设大家都对PopupMenu的使用有一点了解,所以会更多地偏向原理而不讨论使用方法。在讲PopupMenu之前,还是得从ListPopupWindow说起
ListPopupWindow
ListPopupWindow是supportv7包提供的控件,以popup的方式展示一个list,所以说,ListPopupWindow本质上维护了一个PopupWindow并将其内容设置为一个ListView,我们可以看一下他的show()方法的代码
/**
* Show the popup list. If the list is already showing, this method
* will recalculate the popup's size and position.
*/
public void show() {
//在这里动态创建了popupwindow要展示的ListView
int height = buildDropDown();
...
//如果正在显示
if (mPopup.isShowing()) {
...
...
//重新算了一波宽高位置并更新了下
mPopup.update(getAnchorView(), mDropDownHorizontalOffset,
mDropDownVerticalOffset, (widthSpec < 0)? -1 : widthSpec,
(heightSpec < 0)? -1 : heightSpec);
}else {
//开始算宽度
final int widthSpec;
//如果是设置的宽度是MATCH_PARENT就按MATCH_PARENT来
if (mDropDownWidth == ViewGroup.LayoutParams.MATCH_PARENT) {
widthSpec = ViewGroup.LayoutParams.MATCH_PARENT;
} else {
//如果设置的是WRAP_CONTENT就按AnchorView的宽度来
if (mDropDownWidth == ViewGroup.LayoutParams.WRAP_CONTENT) {
widthSpec = getAnchorView().getWidth();
} else {
//或者是传进去一个确定的宽度就听你的
widthSpec = mDropDownWidth;
}
}
//算了下高度
final int heightSpec;
...
mPopup.setWidth(widthSpec);
mPopup.setHeight(heightSpec);
...
//调用了显示PopupWindow的代码
PopupWindowCompat.showAsDropDown(mPopup, getAnchorView(), mDropDownHorizontalOffset,
mDropDownVerticalOffset, mDropDownGravity);
...
}
删掉了很多细节的代码,保留了函数大概的结构,在函数的开始就创建了一个listview并作为PopupWindow的content,如果你对这个buildDropDown()不是很放心,那我们就进来看一下
private int buildDropDown() {
ViewGroup dropDownView;
int otherHeights = 0;
if (mDropDownList == null) {
Context context = mContext;
...
...
//这个DropDownListView是一个ListView的子类
//从这里开始创建并初始化它
mDropDownList = new DropDownListView(context, !mModal);
if (mDropDownListHighlight != null) {
mDropDownList.setSelector(mDropDownListHighlight);
}
//这里的这个adpter是由使用者实例化并传入的
mDropDownList.setAdapter(mAdapter);
mDropDownList.setOnItemClickListener(mItemClickListener);
mDropDownList.setFocusable(true);
mDropDownList.setFocusableInTouchMode(true);
mDropDownList.setOnItemSelectedListener(new AdapterView.OnItemSelectedListener() {
public void onItemSelected(AdapterView<?> parent, View view,
int position, long id) {
if (position != -1) {
DropDownListView dropDownList = mDropDownList;
if (dropDownList != null) {
dropDownList.mListSelectionHidden = false;
}
}
}
public void onNothingSelected(AdapterView<?> parent) {
}
});
mDropDownList.setOnScrollListener(mScrollListener);
if (mItemSelectedListener != null) {
mDropDownList.setOnItemSelectedListener(mItemSelectedListener);
}
dropDownView = mDropDownList;
//他也支持在菜单的顶部或底部加一个用于提示的hintview
//做法就是把hintView跟ListView放到一个LinearLayout里作为dropDownView
View hintView = mPromptView;
...
...
//把这个listview(准确说是dropDownView)放到popupWindow里了
mPopup.setContentView(dropDownView);
} else {
dropDownView = (ViewGroup) mPopup.getContentView();
...
}
...
// Max height avail