楼主是在平板上測试的。图片略微有点大,大家看看效果就好

接下来贴源代码:

PinnedHeaderExpandableListView.java

要注意的是 在 onGroupClick方法中parent.setSelectedGroup(groupPosition)这句代码的作用是点击分组置顶,

我这边不须要这个效果。QQ也没实用到,所以给凝视了。大家假设须要能够解开凝视

package com.xiaos.view;

import android.content.Context;
import android.graphics.Canvas;
import android.util.AttributeSet;
import android.view.MotionEvent;
import android.view.View;
import android.view.ViewGroup;
import android.widget.AbsListView;
import android.widget.AbsListView.OnScrollListener;
import android.widget.ExpandableListAdapter;
import android.widget.ExpandableListView;
import android.widget.ExpandableListView.OnGroupClickListener; public class PinnedHeaderExpandableListView extends ExpandableListView implements OnScrollListener,OnGroupClickListener {
public PinnedHeaderExpandableListView(Context context, AttributeSet attrs, int defStyle) {
super(context, attrs, defStyle);
registerListener();
} public PinnedHeaderExpandableListView(Context context, AttributeSet attrs) {
super(context, attrs);
registerListener();
} public PinnedHeaderExpandableListView(Context context) {
super(context);
registerListener();
} /**
* Adapter 接口 . 列表必须实现此接口 .
*/
public interface HeaderAdapter {
public static final int PINNED_HEADER_GONE = 0;
public static final int PINNED_HEADER_VISIBLE = 1;
public static final int PINNED_HEADER_PUSHED_UP = 2; /**
* 获取 Header 的状态
* @param groupPosition
* @param childPosition
* @return PINNED_HEADER_GONE,PINNED_HEADER_VISIBLE,PINNED_HEADER_PUSHED_UP 当中之中的一个
*/
int getHeaderState(int groupPosition, int childPosition); /**
* 配置 Header, 让 Header 知道显示的内容
* @param header
* @param groupPosition
* @param childPosition
* @param alpha
*/
void configureHeader(View header, int groupPosition,int childPosition, int alpha); /**
* 设置组按下的状态
* @param groupPosition
* @param status
*/
void setGroupClickStatus(int groupPosition, int status); /**
* 获取组按下的状态
* @param groupPosition
* @return
*/
int getGroupClickStatus(int groupPosition); } private static final int MAX_ALPHA = 255; private HeaderAdapter mAdapter; /**
* 用于在列表头显示的 View,mHeaderViewVisible 为 true 才可见
*/
private View mHeaderView; /**
* 列表头是否可见
*/
private boolean mHeaderViewVisible; private int mHeaderViewWidth; private int mHeaderViewHeight; public void setHeaderView(View view) {
mHeaderView = view;
AbsListView.LayoutParams lp = new AbsListView.LayoutParams(
ViewGroup.LayoutParams.MATCH_PARENT, ViewGroup.LayoutParams.WRAP_CONTENT);
view.setLayoutParams(lp); if (mHeaderView != null) {
setFadingEdgeLength(0);
} requestLayout();
} private void registerListener() {
setOnScrollListener(this);
setOnGroupClickListener(this);
} /**
* 点击 HeaderView 触发的事件
*/
private void headerViewClick() {
long packedPosition = getExpandableListPosition(this.getFirstVisiblePosition()); int groupPosition = ExpandableListView.getPackedPositionGroup(packedPosition); if (mAdapter.getGroupClickStatus(groupPosition) == 1) {
this.collapseGroup(groupPosition);
mAdapter.setGroupClickStatus(groupPosition, 0);
}
else{
this.expandGroup(groupPosition);
mAdapter.setGroupClickStatus(groupPosition, 1);
} this.setSelectedGroup(groupPosition);
} private float mDownX;
private float mDownY; /**
* 假设 HeaderView 是可见的 , 此函数用于推断是否点击了 HeaderView, 并对做对应的处理 ,
* 由于 HeaderView 是画上去的 , 所以设置事件监听是无效的 , 仅仅有自行控制 .
*/
@Override
public boolean onTouchEvent(MotionEvent ev) {
if (mHeaderViewVisible) {
switch (ev.getAction()) {
case MotionEvent.ACTION_DOWN:
mDownX = ev.getX();
mDownY = ev.getY();
if (mDownX <= mHeaderViewWidth && mDownY <= mHeaderViewHeight) {
return true;
}
break;
case MotionEvent.ACTION_UP:
float x = ev.getX();
float y = ev.getY();
float offsetX = Math.abs(x - mDownX);
float offsetY = Math.abs(y - mDownY);
// 假设 HeaderView 是可见的 , 点击在 HeaderView 内 , 那么触发 headerClick()
if (x <= mHeaderViewWidth && y <= mHeaderViewHeight
&& offsetX <= mHeaderViewWidth && offsetY <= mHeaderViewHeight) {
if (mHeaderView != null) {
headerViewClick();
} return true;
}
break;
default:
break;
}
} return super.onTouchEvent(ev); } @Override
public void setAdapter(ExpandableListAdapter adapter) {
super.setAdapter(adapter);
mAdapter = (HeaderAdapter) adapter;
} /**
*
* 点击了 Group 触发的事件 , 要依据依据当前点击 Group 的状态来
*/
@Override
public boolean onGroupClick(ExpandableListView parent,View v,int groupPosition,long id) {
if (mAdapter.getGroupClickStatus(groupPosition) == 0) {
mAdapter.setGroupClickStatus(groupPosition, 1);
parent.expandGroup(groupPosition);
//Header自己主动置顶
//parent.setSelectedGroup(groupPosition); } else if (mAdapter.getGroupClickStatus(groupPosition) == 1) {
mAdapter.setGroupClickStatus(groupPosition, 0);
parent.collapseGroup(groupPosition);
} // 返回 true 才干够弹回第一行 , 不知道为什么
return true;
} @Override
protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
super.onMeasure(widthMeasureSpec, heightMeasureSpec);
if (mHeaderView != null) {
measureChild(mHeaderView, widthMeasureSpec, heightMeasureSpec);
mHeaderViewWidth = mHeaderView.getMeasuredWidth();
mHeaderViewHeight = mHeaderView.getMeasuredHeight();
}
} private int mOldState = -1; @Override
protected void onLayout(boolean changed, int left, int top, int right,int bottom) {
super.onLayout(changed, left, top, right, bottom);
final long flatPostion = getExpandableListPosition(getFirstVisiblePosition());
final int groupPos = ExpandableListView.getPackedPositionGroup(flatPostion);
final int childPos = ExpandableListView.getPackedPositionChild(flatPostion);
int state = mAdapter.getHeaderState(groupPos, childPos);
if (mHeaderView != null && mAdapter != null && state != mOldState) {
mOldState = state;
mHeaderView.layout(0, 0, mHeaderViewWidth, mHeaderViewHeight);
} configureHeaderView(groupPos, childPos);
} public void configureHeaderView(int groupPosition, int childPosition) {
if (mHeaderView == null || mAdapter == null
|| ((ExpandableListAdapter) mAdapter).getGroupCount() == 0) {
return;
} int state = mAdapter.getHeaderState(groupPosition, childPosition); switch (state) {
case HeaderAdapter.PINNED_HEADER_GONE: {
mHeaderViewVisible = false;
break;
} case HeaderAdapter.PINNED_HEADER_VISIBLE: {
mAdapter.configureHeader(mHeaderView, groupPosition,childPosition, MAX_ALPHA); if (mHeaderView.getTop() != 0){
mHeaderView.layout(0, 0, mHeaderViewWidth, mHeaderViewHeight);
} mHeaderViewVisible = true; break;
} case HeaderAdapter.PINNED_HEADER_PUSHED_UP: {
View firstView = getChildAt(0);
int bottom = firstView.getBottom(); // intitemHeight = firstView.getHeight();
int headerHeight = mHeaderView.getHeight(); int y; int alpha; if (bottom < headerHeight) {
y = (bottom - headerHeight);
alpha = MAX_ALPHA * (headerHeight + y) / headerHeight;
} else {
y = 0;
alpha = MAX_ALPHA;
} mAdapter.configureHeader(mHeaderView, groupPosition,childPosition, alpha); if (mHeaderView.getTop() != y) {
mHeaderView.layout(0, y, mHeaderViewWidth, mHeaderViewHeight + y);
} mHeaderViewVisible = true;
break;
}
}
} @Override
/**
* 列表界面更新时调用该方法(如滚动时)
*/
protected void dispatchDraw(Canvas canvas) {
super.dispatchDraw(canvas);
if (mHeaderViewVisible) {
//分组栏是直接绘制到界面中。而不是增加到ViewGroup中
drawChild(canvas, mHeaderView, getDrawingTime());
}
} @Override
public void onScroll(AbsListView view, int firstVisibleItem,int visibleItemCount, int totalItemCount) {
final long flatPos = getExpandableListPosition(firstVisibleItem);
int groupPosition = ExpandableListView.getPackedPositionGroup(flatPos);
int childPosition = ExpandableListView.getPackedPositionChild(flatPos); configureHeaderView(groupPosition, childPosition);
} @Override
public void onScrollStateChanged(AbsListView view, int scrollState) {
}
}

PinnedHeaderExpandableAdapter.java 适配器

实现了PinnedHeaderExpandableListView中HeaderAdapter接口

package com.xiaos.adapter;

import android.content.Context;
import android.util.SparseIntArray;
import android.view.LayoutInflater;
import android.view.View;
import android.view.ViewGroup;
import android.widget.BaseExpandableListAdapter;
import android.widget.ImageView;
import android.widget.TextView; import com.xiaos.pinnedheaderexpandable.R;
import com.xiaos.view.PinnedHeaderExpandableListView;
import com.xiaos.view.PinnedHeaderExpandableListView.HeaderAdapter; public class PinnedHeaderExpandableAdapter extends BaseExpandableListAdapter implements HeaderAdapter{
private String[][] childrenData;
private String[] groupData;
private Context context;
private PinnedHeaderExpandableListView listView;
private LayoutInflater inflater; public PinnedHeaderExpandableAdapter(String[][] childrenData,String[] groupData
,Context context,PinnedHeaderExpandableListView listView){
this.groupData = groupData;
this.childrenData = childrenData;
this.context = context;
this.listView = listView;
inflater = LayoutInflater.from(this.context);
} @Override
public Object getChild(int groupPosition, int childPosition) {
return childrenData[groupPosition][childPosition];
} @Override
public long getChildId(int groupPosition, int childPosition) {
return 0;
} @Override
public View getChildView(int groupPosition, int childPosition,
boolean isLastChild, View convertView, ViewGroup parent) {
View view = null;
if (convertView != null) {
view = convertView;
} else {
view = createChildrenView();
}
TextView text = (TextView)view.findViewById(R.id.childto);
text.setText(childrenData[groupPosition][childPosition]);
return view;
} @Override
public int getChildrenCount(int groupPosition) {
return childrenData[groupPosition].length;
} @Override
public Object getGroup(int groupPosition) {
return groupData[groupPosition];
} @Override
public int getGroupCount() {
return groupData.length;
} @Override
public long getGroupId(int groupPosition) {
return 0;
} @Override
public View getGroupView(int groupPosition, boolean isExpanded,
View convertView, ViewGroup parent) {
View view = null;
if (convertView != null) {
view = convertView;
} else {
view = createGroupView();
} ImageView iv = (ImageView)view.findViewById(R.id.groupIcon); if (isExpanded) {
iv.setImageResource(R.drawable.btn_browser2);
}
else{
iv.setImageResource(R.drawable.btn_browser);
} TextView text = (TextView)view.findViewById(R.id.groupto);
text.setText(groupData[groupPosition]);
return view;
} @Override
public boolean hasStableIds() {
return true;
} @Override
public boolean isChildSelectable(int groupPosition, int childPosition) {
return true;
} private View createChildrenView() {
return inflater.inflate(R.layout.child, null);
} private View createGroupView() {
return inflater.inflate(R.layout.group, null);
} @Override
public int getHeaderState(int groupPosition, int childPosition) {
final int childCount = getChildrenCount(groupPosition);
if (childPosition == childCount - 1) {
return PINNED_HEADER_PUSHED_UP;
} else if (childPosition == -1
&& !listView.isGroupExpanded(groupPosition)) {
return PINNED_HEADER_GONE;
} else {
return PINNED_HEADER_VISIBLE;
}
} @Override
public void configureHeader(View header, int groupPosition,
int childPosition, int alpha) {
String groupData = this.groupData[groupPosition];
((TextView) header.findViewById(R.id.groupto)).setText(groupData); } private SparseIntArray groupStatusMap = new SparseIntArray(); @Override
public void setGroupClickStatus(int groupPosition, int status) {
groupStatusMap.put(groupPosition, status);
} @Override
public int getGroupClickStatus(int groupPosition) {
if (groupStatusMap.keyAt(groupPosition)>=0) {
return groupStatusMap.get(groupPosition);
} else {
return 0;
}
}
}

MainActivity.java主Activity

package com.xiaos.pinnedheaderexpandable;

import android.app.Activity;
import android.os.Bundle;
import android.view.View;
import android.widget.ExpandableListView;
import android.widget.ExpandableListView.OnGroupClickListener; import com.xiaos.adapter.PinnedHeaderExpandableAdapter;
import com.xiaos.view.PinnedHeaderExpandableListView; public class MainActivity extends Activity{
private PinnedHeaderExpandableListView explistview;
private String[][] childrenData = new String[10][10];
private String[] groupData = new String[10];
private int expandFlag = -1;//控制列表的展开
private PinnedHeaderExpandableAdapter adapter; @Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.layout_main);
initView();
initData();
} /**
* 初始化VIEW
*/
private void initView() {
explistview = (PinnedHeaderExpandableListView)findViewById(R.id.explistview);
} /**
* 初始化数据
*/
private void initData() {
for(int i=0;i<10;i++){
groupData[i] = "分组"+i;
} for(int i=0;i<10;i++){
for(int j=0;j<10;j++){
childrenData[i][j] = "好友"+i+"-"+j;
}
}
//设置悬浮头部VIEW
explistview.setHeaderView(getLayoutInflater().inflate(R.layout.group_head,
explistview, false));
adapter = new PinnedHeaderExpandableAdapter(childrenData, groupData, getApplicationContext(),explistview);
explistview.setAdapter(adapter); //设置单个分组展开
//explistview.setOnGroupClickListener(new GroupClickListener());
} class GroupClickListener implements OnGroupClickListener{
@Override
public boolean onGroupClick(ExpandableListView parent, View v,
int groupPosition, long id) {
if (expandFlag == -1) {
// 展开被选的group
explistview.expandGroup(groupPosition);
// 设置被选中的group置于顶端
explistview.setSelectedGroup(groupPosition);
expandFlag = groupPosition;
} else if (expandFlag == groupPosition) {
explistview.collapseGroup(expandFlag);
expandFlag = -1;
} else {
explistview.collapseGroup(expandFlag);
// 展开被选的group
explistview.expandGroup(groupPosition);
// 设置被选中的group置于顶端
explistview.setSelectedGroup(groupPosition);
expandFlag = groupPosition;
}
return true;
}
}
}

布局文件

child.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="40dip"
android:background="#FFFFFF" > <ImageView
android:id="@+id/groupIcon"
android:layout_width="40dp"
android:layout_height="40dp"
android:paddingLeft="10dp"
android:src="@null" /> <TextView
android:id="@+id/childto"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:paddingLeft="10dp"
android:paddingTop="10dip"
android:textColor="#000000"
android:textSize="16sp" /> </LinearLayout>

group_head.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="wrap_content"
android:orientation="horizontal"
android:background="#B8E6FA"
android:gravity="center_vertical">
<ImageView
android:id="@+id/groupIcon"
android:layout_width="50dp"
android:layout_height="50dp"
android:contentDescription="@null"
android:layout_marginLeft="10dp"
android:src="@drawable/btn_browser2"/>
<TextView
android:id="@+id/groupto"
android:layout_width="match_parent"
android:layout_height="50dp"
android:textColor="#000000"
android:textSize="18sp"
android:gravity="center_vertical|left"/> </LinearLayout>

group.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="wrap_content"
android:orientation="horizontal"
android:background="#B8E6FA"
android:gravity="center_vertical">
<ImageView
android:id="@+id/groupIcon"
android:layout_width="50dp"
android:layout_height="50dp"
android:contentDescription="@null"
android:layout_marginLeft="10dp"
android:src="@drawable/btn_browser"/>
<TextView
android:id="@+id/groupto"
android:layout_width="match_parent"
android:layout_height="50dp"
android:textColor="#000000"
android:textSize="18sp"
android:gravity="center_vertical|left"/>
</LinearLayout>

layout_main.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:background="#F0F0F0"
android:orientation="vertical" > <com.xiaos.view.PinnedHeaderExpandableListView
android:id="@+id/explistview"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:layout_marginLeft="0.0dip"
android:cacheColorHint="#00000000"
android:choiceMode="singleChoice"
android:drawSelectorOnTop="false"
android:fastScrollEnabled="false"
android:footerDividersEnabled="true"
android:groupIndicator="@null"
android:scrollbars="vertical"
android:scrollingCache="true" /> </LinearLayout>

AndroidManifest.xml

<manifest xmlns:android="http://schemas.android.com/apk/res/android"
package="com.xiaos.pinnedheaderexpandable"
android:versionCode="1"
android:versionName="1.0" > <uses-sdk
android:minSdkVersion="8"
android:targetSdkVersion="19" /> <application
android:allowBackup="true"
android:icon="@drawable/ic_launcher"
android:label="@string/app_name"
android:theme="@style/AppTheme" >
<activity
android:name=".MainActivity"
android:icon="@drawable/ic_launcher">
<intent-filter>
<action android:name="android.intent.action.MAIN"/>
<category android:name="android.intent.category.LAUNCHER"/>
</intent-filter>
</activity>
</application> </manifest>

两张图片:

     

watermark/2/text/aHR0cDovL2Jsb2cuY3Nkbi5uZXQvaDc4NzAxODE=/font/5a6L5L2T/fontsize/400/fill/I0JBQkFCMA==/dissolve/70/gravity/Center" alt="">

下载地址: http://download.csdn.net/detail/h7870181/8073677

Android UI视图效果篇之仿QQ好友列表分组悬浮PinnedHeaderExpandableListView的更多相关文章

  1. 仿QQ好友列表界面的实现

    TableView有2种style:UITableViewStylePlain 和 UITableViewStyleGrouped. 但是QQ好友列表的tableView给人的感觉似乎是2个style ...

  2. ExpandableListView仿QQ好友列表

    本例中,对ExpandableListView中的数据进行了封装,分为两个JavaBean,一个为Group类表示组信息,一个Child类表示该组下子列表信息: Group: public class ...

  3. Objective-c——UI基础开发第九天(QQ好友列表)

    一.知识点: 1.双模型的嵌套使用 2.Button的对齐方式 3.优化UITableView的加载 4.layoutSubview的使用 5.cell的折叠代理 二.双模型的嵌套定义: 注意是将se ...

  4. android 实现QQ好友列表

    在某些Android开发群里,看到有些新手问怎么实现QQ好友列表,其实网上一搜挺多的.接触Android,也才一年的时间,大部分时间花在工作上(解bug...),界面上开发很少参与.自己维护的系统应用 ...

  5. iOS开发UI篇—使用UItableview完成一个简单的QQ好友列表(一)

    iOS开发UI篇—使用UItableview完成一个简单的QQ好友列表(一) 一.项目结构和plist文件 二.实现代码 1.说明: 主控制器直接继承UITableViewController // ...

  6. Android特效专辑(六)——仿QQ聊天撒花特效,无形装逼,最为致命

    Android特效专辑(六)--仿QQ聊天撒花特效,无形装逼,最为致命 我的关于特效的专辑已经在CSDN上申请了一个专栏--http://blog.csdn.net/column/details/li ...

  7. 动手分析安卓仿QQ联系人列表TreeView控件

    因项目需要需要用到仿QQ联系人列表的控件样式,于是网上找到一个轮子(https://github.com/TealerProg/TreeView),工作完成现在简单分析一下这个源码.   一. 需要用 ...

  8. WPF 自定义TreeView控件样式,仿QQ联系人列表

    一.前言 TreeView控件在项目中使用比较频繁,普通的TreeView并不能满足我们的需求.因此我们需要滴对TreeView进行改造.下面的内容将介绍仿QQ联系人TreeView样式及TreeVi ...

  9. 基于Qt的相似QQ好友列表抽屉效果的实现

    版权声明:本文为博主原创文章.未经博主同意不得转载. https://blog.csdn.net/shuideyidi/article/details/30619167     前段时间在忙毕业设计, ...

随机推荐

  1. Hibernate(十一)检索

    一.Hibernate检索策略 二.检索方法 三.get和load比较 get和load的区别:  get不支持延迟加载,而load支持.  当查询特定的数据库中不存在的数据时,get会返回null, ...

  2. Camera2必知必会

    引言 一切源于在项目过程中的一个Bug:我的需求是在MainActivity 实现自动预览,也可以点击跳到签到SignedActivity去实现拍照签到,第一次进入界面的时候都是正常的,但是有时候返回 ...

  3. 'htmlentities(): charset `utf8' not supported, assuming utf-8'

    TP5.1框架报错!  Fatal error: Uncaught exception 'think\exception\ErrorException' with message 'htmlentit ...

  4. 安装调试Installing Odoo

    来自odoo的安装步骤 There are mutliple ways to install Odoo, or not install it at all, depending on the inte ...

  5. 在Ubuntu Server是配置iptables防火墙

    iptables 是一个安装在Ubuntu Server上的默认防火墙.在正常的ubuntu安装过程中,iptables是被安装上了的,但是它默认允许所有的流量(不管防火墙是否是无效的) 关于ipta ...

  6. idea 配置多个tomcat

      1.打开设置窗口 File-->Settings 2.启用tomcat 3.run-->edit configuration: 3.点击左上角+号-->找到Tomcat Serv ...

  7. 深入浅出Java垃圾回收机制(一)(转载)

    转载来做笔记的:原文地址:http://www.importnew.com/1993.html. 对于Java开发人员来说,了解垃圾回收机制(GC)有哪些好处呢?首先可以满足作为一名软件工程师的求知欲 ...

  8. centos7编译python3.6与原有的2.7共存

    在某些场景下我们可能需要python2.7和python3这两个版本进行共存,我在工作中也遇到过这样的问题,所以今天来总结下我的安装过程, 我的是用源码包安装的,安装的是python3.6的版本. 安 ...

  9. 赵雅智_android多线程下载带进度条

    progressBar说明 在某些操作的进度中的可视指示器,为用户呈现操作的进度,还它有一个次要的进度条,用来显示中间进度,如在流媒体播放的缓冲区的进度. 一个进度条也可不确定其进度.在不确定模式下, ...

  10. hdu 4031(树状数组+辅助数组)

    题目链接:http://acm.hdu.edu.cn/showproblem.php?pid=4031 Attack Time Limit: 5000/3000 MS (Java/Others)    ...