最近项目需要,于是自己实现了一个带导航栏的通讯录,上代码!

一、数据准备

(1)bean:

public class Friend {
private String remark;
private String account;
private String nickName;
private String phoneNumber;
private String area;
private String headerUrl;
private String pinyin; public String getPinyin() {
return pinyin;
} public void setPinyin(String pinyin) {
this.pinyin = pinyin;
} public void setAccount(String account) {
this.account = account;
} public String getAccount() {
return account;
} public void setArea(String area) {
this.area = area;
} public String getArea() {
return area;
} public void setNickName(String nickName) {
this.nickName = nickName;
} public String getNickName() {
return nickName;
} public String getPhoneNumber() {
return phoneNumber;
} public void setPhoneNumber(String phoneNumber) {
this.phoneNumber = phoneNumber;
} public String getRemark() {
return remark;
} public void setRemark(String remark) {
this.remark = remark;
} public void setHeaderUrl(String headerUrl) {
this.headerUrl = headerUrl;
} public String getHeaderUrl() {
return headerUrl;
} public String getFirstPinyin(){
return pinyin!=null?pinyin.substring(0,1):"";
}
}

(2)测试数据:

从网上找了一堆姓名存进一个txt文件中,并放在raw文件下,然后在程序运行时读入到List中:

private List<Friend> getFriendList(){
List<Friend> friends=new ArrayList<>();
InputStream inputStream=getResources().openRawResource(R.raw.names);
BufferedReader reader=new BufferedReader(new InputStreamReader(inputStream));
String input=null;
try {
while ((input=reader.readLine())!=null){
Friend friend=new Friend();
friend.setAccount(input);
String pinyin=Pinyin4jUtil.convertToFirstSpell(input);
if (Pinyin4jUtil.isPinYin(pinyin)){
friend.setPinyin(pinyin);
}else {
friend.setPinyin("#");
}
friends.add(friend);
}
if (friends.size()>1){
Collections.sort(friends,new PinYinComparator());
}
} catch (IOException e) {
e.printStackTrace();
}
return friends;
}

注意:

a.关于拼音的处理,这里使用的是pinyin4j,对录入的数据进行拼音转化和判断,若不是拼音则存入“#”,为的是后面将一些将用户名存储为数字和特殊符号的数据能够排到最后面。

附上拼音转换和判断代码:

public class Pinyin4jUtil {
public static String convertToFirstSpell(String chinese){
StringBuffer pinyinName=new StringBuffer();
char[] nameChar=chinese.toCharArray();
HanyuPinyinOutputFormat defaultFormat=new HanyuPinyinOutputFormat();
defaultFormat.setCaseType(HanyuPinyinCaseType.UPPERCASE);
defaultFormat.setToneType(HanyuPinyinToneType.WITHOUT_TONE);
for (char c:nameChar){
if (c>128){
try {
String[] strs= PinyinHelper.toHanyuPinyinStringArray(c,defaultFormat);
if (strs!=null){
for (int i=0;i<strs.length;i++){
pinyinName.append(strs[i].charAt(0));
if (i!=strs.length-1){
pinyinName.append(",");
}
}
}
} catch (BadHanyuPinyinOutputFormatCombination badHanyuPinyinOutputFormatCombination) {
badHanyuPinyinOutputFormatCombination.printStackTrace();
}
}else {
pinyinName.append(c);
}
pinyinName.append(" ");
}
return parseTheChineseByObject(discountTheChinese(pinyinName.toString()));
} public static String convertToSpell(String chinese){
StringBuffer pinyinName=new StringBuffer();
char[] nameChar=chinese.toCharArray();
HanyuPinyinOutputFormat defaultFormat=new HanyuPinyinOutputFormat();
defaultFormat.setCaseType(HanyuPinyinCaseType.LOWERCASE);
defaultFormat.setToneType(HanyuPinyinToneType.WITHOUT_TONE);
for (char c:nameChar){
if (c>128){
try {
String[] strs= PinyinHelper.toHanyuPinyinStringArray(c,defaultFormat);
if (strs!=null){
for (int i=0;i<strs.length;i++){
pinyinName.append(strs[i]);
if (i!=strs.length-1){
pinyinName.append(",");
}
}
}
} catch (BadHanyuPinyinOutputFormatCombination badHanyuPinyinOutputFormatCombination) {
badHanyuPinyinOutputFormatCombination.printStackTrace();
}
}else {
pinyinName.append(c);
}
pinyinName.append(" ");
}
return parseTheChineseByObject(discountTheChinese(pinyinName.toString()));
} public static List<String> convertToSpellList(String chinese){
StringBuffer pinyinName=new StringBuffer();
char[] nameChar=chinese.toCharArray();
HanyuPinyinOutputFormat defaultFormat=new HanyuPinyinOutputFormat();
defaultFormat.setCaseType(HanyuPinyinCaseType.LOWERCASE);
defaultFormat.setToneType(HanyuPinyinToneType.WITHOUT_TONE);
for (char c:nameChar){
if (c>128){
try {
String[] strs= PinyinHelper.toHanyuPinyinStringArray(c,defaultFormat);
if (strs!=null){
for (int i=0;i<strs.length;i++){
pinyinName.append(strs[i]);
if (i!=strs.length-1){
pinyinName.append(",");
}
}
}
} catch (BadHanyuPinyinOutputFormatCombination badHanyuPinyinOutputFormatCombination) {
badHanyuPinyinOutputFormatCombination.printStackTrace();
}
}else {
pinyinName.append(c);
}
pinyinName.append(" ");
}
return parseTheChineseByObjectToList(discountTheChinese(pinyinName.toString()));
} private static List<Map<String ,Integer>> discountTheChinese(String theStr){
List<Map<String,Integer>> mapList=new ArrayList<Map<String, Integer>>();
Map<String,Integer> onlyOne=null;
String[] firsts=theStr.split(" ");
for (String str:firsts){
onlyOne=new Hashtable<String, Integer>();
String[] china=str.split(",");
for (String s:china){
Integer count=onlyOne.get(s);
if (count==null){
onlyOne.put(s,new Integer(1));
}else {
onlyOne.remove(s);
count++;
onlyOne.put(s,count);
}
}
mapList.add(onlyOne);
}
return mapList;
} private static String parseTheChineseByObject(List<Map<String,Integer>> list){
Map<String,Integer> first=null;
for (int i=0;i<list.size();i++){
Map<String,Integer> temp=new Hashtable<String, Integer>();
if (first!=null){
for (String s:first.keySet()){
for (String s1:list.get(i).keySet()){
String str=s+s1;
temp.put(str,1);
}
}
if (temp!=null&&temp.size()>0){
first.clear();
}
}else {
for (String s:list.get(i).keySet()){
String str=s;
temp.put(str,1);
}
}
if (temp!=null&&temp.size()>0){
first=temp;
}
}
String returnStr="";
List<String> returnList=new ArrayList<>();
if (first!=null){
for (String str:first.keySet()){
returnStr+=(str+" ");
returnList.add(str);
}
}
if (returnStr.length()>0){
returnStr=returnStr.substring(0,returnStr.length()-1);
}
return returnList.get(0);
} private static List<String> parseTheChineseByObjectToList(List<Map<String,Integer>> list){
Map<String,Integer> first=null;
for (int i=0;i<list.size();i++){
Map<String,Integer> temp=new Hashtable<String, Integer>();
if (first!=null){
for (String s:first.keySet()){
for (String s1:list.get(i).keySet()){
String str=s+s1;
temp.put(str,1);
}
}
if (temp!=null&&temp.size()>0){
first.clear();
}
}else {
for (String s:list.get(i).keySet()){
String str=s;
temp.put(str,1);
}
}
if (temp!=null&&temp.size()>0){
first=temp;
}
}
List<String> returnList=new ArrayList<>();
if (first!=null){
for (String str:first.keySet()){
returnList.add(str);
}
}
return returnList;
} public static boolean isPinYin(String string){
char[] chars=string.toCharArray();
for (char c:chars){
if ((c>=65&&c<=90)||(c>=97&&c<=122)){ }else {
return false;
}
}
return true;
}
}

b.在上面录入数据到List中之后需要对数据按照字典序进行排序,使用的是comparator接口:

public class PinYinComparator implements Comparator<Friend> {
@Override
public int compare(Friend o1, Friend o2) {
if (o1.getPinyin().equals("#")){
return 1;
}else if (o2.getPinyin().equals("#")){
return -1;
}
return o1.getPinyin().compareToIgnoreCase(o2.getPinyin());
}
}

二、RecyclerView和Adapter的使用

这里不对RecycleView做过多介绍,主要是在Adapter的使用上有所不同,需要在onBindViewHolder中做一些判断。

对于不同拼音首字母开头的分组,并且产生如下效果的解决方法是:

a.列表项布局文件的处理:

在每一个列表布局中都放一个首字母头的布局,只是设为gone

<?xml version="1.0" encoding="utf-8"?>
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
android:orientation="vertical"
android:layout_width="match_parent"
android:layout_height="wrap_content">
<LinearLayout
android:id="@+id/stick_container"
android:visibility="gone"
android:orientation="vertical"
android:layout_width="match_parent"
android:layout_height="40dp"
android:background="@color/txtGray"
android:gravity="center_vertical"
>
<TextView
android:id="@+id/header"
android:text="A"
android:textSize="20sp"
android:layout_marginLeft="20dp"
android:layout_width="wrap_content"
android:layout_height="wrap_content" />
</LinearLayout>
<LinearLayout
android:gravity="center"
android:layout_width="match_parent"
android:layout_height="80dp">
<TextView
android:id="@+id/name"
android:textSize="25sp"
android:text="name"
android:layout_width="wrap_content"
android:layout_height="wrap_content" />
</LinearLayout> </LinearLayout>

b.onBindViewHolder的判断处理

Friend friend=getItem(position);
((TextView)holder.getView(R.id.name)).setText(friend.getAccount());
if (position==0){
holder.getView(R.id.stick_container).setVisibility(View.VISIBLE);
((TextView)holder.getView(R.id.header)).setText(friend.getFirstPinyin());
}else {
if (!TextUtils.equals(friend.getFirstPinyin(),getItem(position-1).getFirstPinyin())){
holder.getView(R.id.stick_container).setVisibility(View.VISIBLE);
((TextView)holder.getView(R.id.header)).setText(friend.getFirstPinyin());
}else {
holder.getView(R.id.stick_container).setVisibility(View.GONE);
}
}
holder.itemView.setContentDescription(friend.getFirstPinyin());

三、首字母导航栏实现

效果如下

右边的首字母导航栏采用自定义View实现,代码如下:

public class PinYinSlideView extends View implements View.OnTouchListener{

    private Paint textPaint;
private Paint backgroundPaint;
private Paint circlePaint,circleTextPaint;
private int height;
private float textHeight;
private float paddingHeight;
private float radius;
private float backgroundSize;
private boolean hasTouch;
private float lastY,lastX;
private float screenX,screenY;
private OnShowTextListener onShowTextListener; public PinYinSlideView(Context context) {
this(context,null);
} public PinYinSlideView(Context context, AttributeSet attrs) {
super(context, attrs);
initView();
} public void setOnShowTextListener(OnShowTextListener onShowTextListener) {
this.onShowTextListener = onShowTextListener;
} private void initView(){
textPaint=new Paint();
textPaint.setAntiAlias(true);
textPaint.setStyle(Paint.Style.FILL);
textPaint.setColor(getResources().getColor(R.color.txtGray));
float textSize= TypedValue.applyDimension(TypedValue.COMPLEX_UNIT_SP,10,getResources().getDisplayMetrics());
textPaint.setTextSize(textSize);
textPaint.setTextAlign(Paint.Align.CENTER);
Paint.FontMetrics metrics=textPaint.getFontMetrics();
textHeight=metrics.bottom-metrics.top;
backgroundPaint=new Paint();
backgroundPaint.setAntiAlias(true);
backgroundPaint.setStyle(Paint.Style.FILL);
backgroundPaint.setColor(getResources().getColor(R.color.backgroundGray));
backgroundSize=TypedValue.applyDimension(TypedValue.COMPLEX_UNIT_DIP,30,getResources().getDisplayMetrics());
radius=TypedValue.applyDimension(TypedValue.COMPLEX_UNIT_DIP,3,getResources().getDisplayMetrics()); circlePaint=new Paint();
circleTextPaint=new Paint();
circlePaint.setAntiAlias(true);
circleTextPaint.setAntiAlias(true);
circlePaint.setColor(getResources().getColor(R.color.backgroundGray));
circleTextPaint.setColor(getResources().getColor(R.color.txtGray));
circlePaint.setStyle(Paint.Style.FILL);
circlePaint.setStrokeWidth(TypedValue.applyDimension(TypedValue.COMPLEX_UNIT_SP,100,getResources().getDisplayMetrics()));
circleTextPaint.setTextSize(TypedValue.applyDimension(TypedValue.COMPLEX_UNIT_SP,30,getResources().getDisplayMetrics()));
circleTextPaint.setTextAlign(Paint.Align.CENTER);
this.setOnTouchListener(this);
} @Override
public boolean dispatchTouchEvent(MotionEvent event) { return super.dispatchTouchEvent(event);
} @Override
protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
height=MeasureSpec.getSize(heightMeasureSpec);
paddingHeight=(height-28*textHeight)/29;
screenX=getResources().getDisplayMetrics().widthPixels/2;
screenY=height/2;
int mode=MeasureSpec.getMode(widthMeasureSpec);
float size = 0;
switch (mode){
case MeasureSpec.EXACTLY:
size=MeasureSpec.getSize(widthMeasureSpec);
break;
case MeasureSpec.AT_MOST:
size=backgroundSize;
break;
}
setMeasuredDimension((int) size,height);
} @Override
public boolean onTouch(View v, MotionEvent event) {
switch (event.getAction()){
case MotionEvent.ACTION_DOWN:
lastX=event.getX();
lastY=event.getY();
if (lastX>0&&lastX<=backgroundSize){
hasTouch=true;
invalidate();
requestLayout();
}
break;
case MotionEvent.ACTION_MOVE:
lastX=event.getX();
lastY=event.getY();
if (lastX>0&&lastX<=backgroundSize){
hasTouch=true;
invalidate();
requestLayout();
}
break;
case MotionEvent.ACTION_UP:
if (hasTouch){
hasTouch=false;
invalidate();
}
break;
}
return true;
} @Override
protected void onDraw(Canvas canvas) {
char c[]={'↑','A','#'};
float baseY=textHeight;
float baseX=(0+backgroundSize)/2;
float radius1=TypedValue.applyDimension(TypedValue.COMPLEX_UNIT_SP,100,getResources().getDisplayMetrics())/2;
if (hasTouch){
char c1[]={'↑','A','#'};
if (Build.VERSION.SDK_INT>=Build.VERSION_CODES.LOLLIPOP){
canvas.drawRoundRect(0,0,backgroundSize,height,radius,radius,backgroundPaint);
}else {
canvas.drawRect(0,0,backgroundSize,height,backgroundPaint);
}
float offsetY=textHeight+paddingHeight;
for (int i=0;i<28;i++){
if (lastY>=(i*offsetY)&&lastY<=((i+1)*offsetY)){
canvas.drawCircle(screenX,screenY,radius1,circlePaint);
if (i==0){
if (onShowTextListener!=null){
onShowTextListener.showText(String.valueOf(c1[0]));
}
//canvas.drawText(c1,0,1,screenX,screenY+textHeight,circleTextPaint);
}else if (i>0&&i<27){
if (onShowTextListener!=null){
onShowTextListener.showText(String.valueOf(c1[1]));
}
//canvas.drawText(c1,1,1,screenX,screenY+textHeight,circleTextPaint);
}else if (i==27){
if (onShowTextListener!=null){
onShowTextListener.showText(String.valueOf(c1[2]));
}
//canvas.drawText(c1,2,1,screenX,screenY+textHeight,circleTextPaint);
}
break;
//canvas.drawText(c1,0,1,screenX,screenY+textHeight,circleTextPaint);
}
if (i>0&&i<27){
c1[1]++;
}
}
}else {
if (onShowTextListener!=null){
onShowTextListener.showText(null);
}
} for (int i=0;i<28;i++){
if (i==0){
canvas.drawText(c,0,1,baseX,baseY,textPaint);
}else if (i>0&&i<27){
canvas.drawText(c,1,1,baseX,baseY,textPaint);
c[1]++;
}else if (i==27){
canvas.drawText(c,2,1,baseX,baseY,textPaint);
}
baseY+=(paddingHeight+textHeight);
}
} public interface OnShowTextListener{
void showText(String text);
} }

出现在中间的半透明带圆背景字体通过继承TextView实现,代码如下:

public class CircleTextView extends View {

    private Paint circlePaint,circleTextPaint;
private float textHeight;
private String text;
private float radius; public CircleTextView(Context context) {
this(context,null);
} public CircleTextView(Context context, AttributeSet attrs) {
super(context, attrs);
initView();
} private void initView(){
circlePaint=new Paint();
circleTextPaint=new Paint();
circlePaint.setAntiAlias(true);
circleTextPaint.setAntiAlias(true);
circlePaint.setColor(getResources().getColor(R.color.backgroundGray));
circleTextPaint.setColor(getResources().getColor(R.color.txtGray));
circlePaint.setStyle(Paint.Style.FILL);
circlePaint.setStrokeWidth(TypedValue.applyDimension(TypedValue.COMPLEX_UNIT_SP,100,getResources().getDisplayMetrics()));
circleTextPaint.setTextSize(TypedValue.applyDimension(TypedValue.COMPLEX_UNIT_SP,30,getResources().getDisplayMetrics()));
circleTextPaint.setTextAlign(Paint.Align.CENTER);
Paint.FontMetrics metrics=circleTextPaint.getFontMetrics();
textHeight=metrics.bottom-metrics.top;
radius=TypedValue.applyDimension(TypedValue.COMPLEX_UNIT_SP,100,getResources().getDisplayMetrics())/2;
} public void setText(String text) {
this.text = text;
invalidate();
} @Override
protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
setMeasuredDimension((int)radius*2,(int) radius*2);
} @Override
protected void onDraw(Canvas canvas) {
if (text!=null){
canvas.drawCircle(radius,radius,radius,circlePaint);
canvas.drawText(text,radius,radius+textHeight/4,circleTextPaint);
}
}
}

当触摸导航栏时通过监听器OnShowTextListener的showText方法将触摸到的字体的字符传递给CircleTextView显示出来,代码如下:

pinYinSlideView.setOnShowTextListener(new PinYinSlideView.OnShowTextListener() {
@Override
public void showText(String text) {
circleText.setText(text);
if (text!=null){
if (!text.equals("↑")){
int position=0;
boolean hasPinyin=false;
for (int i=0;i<friends.size();i++){
Friend friend=friends.get(i);
if (friend.getFirstPinyin().equals(text)){
position=i;
hasPinyin=true;
break;
}
}
if (hasPinyin){
MainActivity.this.scrollToPosition(position);
}
}else {
MainActivity.this.scrollToPosition(0);
}
} }
});

接下来是重中之重,这里涉及到了RecyclerView的滚动到指定位置问题,当触摸到对应得字母时RecyclerView也要滚动到对应的列表项,这个需要实现RecyclerView的OnScrollListener,还要配合它的scrollToPosition 和 scrollBy 方法来实现:

代码如下:

contactList.addOnScrollListener(new RecyclerView.OnScrollListener() {
@Override
public void onScrolled(RecyclerView recyclerView, int dx, int dy) {
super.onScrolled(recyclerView, dx, dy);
RecyclerView.LayoutManager layoutManager=recyclerView.getLayoutManager();
if (layoutManager instanceof LinearLayoutManager){
int firstItem=((LinearLayoutManager) layoutManager).findFirstVisibleItemPosition();
Friend friend=contactAdapter.getItem(firstItem);
header.setText(friend.getFirstPinyin());
if (move){
move=false;
int n=mIndex-firstItem;
if (n>=0&&n<contactList.getChildCount()){
int top=contactList.getChildAt(n).getTop();
contactList.scrollBy(0,top);
}
}
} } @Override
public void onScrollStateChanged(RecyclerView recyclerView, int newState) {
super.onScrollStateChanged(recyclerView, newState);
}
});

其中:

onScrolled(RecyclerView recyclerView, int dx, int dy) :

滚动时回调,dx指水平滚动距离,dy指垂直滚动距离

onScrollStateChanged(RecyclerView recyclerView, int newState):

滚动状态变化时回调,dx指水平滚动距离,dy指垂直滚动距离,

newState可分为:

SCROLL_STATE_IDLE:目前RecyclerView不滚动

SCROLL_STATE_DRAGGING:当前RecyclerView正被例如用户触摸这样的事件拖动时的状态

SCROLL_STATE_SETTLING:目前RecyclerView正自动滚动中

滚动到对应position指定位置的函数:

private void scrollToPosition(int position){
if (position>=0&&position<=friends.size()-1){
int firstItem=manager.findFirstVisibleItemPosition();
int lastItem=manager.findLastVisibleItemPosition();
if (position<=firstItem){
contactList.scrollToPosition(position);
}else if (position<=lastItem){
int top=contactList.getChildAt(position-firstItem).getTop();
contactList.scrollBy(0,top);
}else {
contactList.scrollToPosition(position);
mIndex=position;
move=true;
}
}
}

到这里介绍为止,介绍的不清楚的或者想知道更多的可以到以下地址获取源码:

https://github.com/liberty2015/ContactListView

如果您觉得不错的请点赞,当然源码和文章中的不足之处也请指出,谢谢。

android 通讯录实现的更多相关文章

  1. Jquery Mobile设计Android通讯录第二章

    本文是jQuery Mobile设计Android通讯录系统教程的第二篇,在上一篇教程中(http://publish.itpub.net/a2011/0517/1191/000001191561.s ...

  2. Android——通讯录

    <?xml version="1.0" encoding="utf-8"?> <LinearLayout xmlns:android=&quo ...

  3. 使用Jquery Mobile设计Android通讯录

    本系列教程将指导大家一步步使用Jquery Mobile设计一个Android的通讯录应用.其中在应用的界面部分,将使用jQuery Mobile框架,并且会指导大家如何使Android中提供的web ...

  4. android通讯录导航栏源码(一)

    通讯录导航栏源码: 1.activity package com.anna.contact.activity; import java.util.ArrayList; import java.util ...

  5. 【实用篇】获取Android通讯录中联系人信息

    第一步,在Main.xml布局文件中声明一个Button控件,布局文件代码如下: <LinearLayout xmlns:android="http://schemas.android ...

  6. Android Contacts (android通讯录读取)-content provider

    Content Provider 在数据处理中,Android通常使用Content Provider的方式.Content Provider使用Uri实例作为句柄的数据封装的,很方便地访问地进行数据 ...

  7. 实例源码--Android通讯录源码

      下载源码   技术要点: 1.通讯录联 系人的管理 2.接听.打电话 3.发短信 4. 源码带详细的 中文注释    ...... 详细介绍: 1.通讯录联系人的管理 播放器具有播放本地音乐的功能 ...

  8. Android通讯录管理(获取联系人、通话记录、短信消息)

    前言:前阵子主要是记录了如何对联系人的一些操作,比如搜索,全选.反选和删除等在实际开发中可能需要实现的功能,本篇博客是小巫从一个别人开源的一个项目抽取出来的部分内容,把它给简化出来,可以让需要的朋友清 ...

  9. android UI进阶之弹窗的使用(2)--实现通讯录的弹窗效果

    相信大家都体验过android通讯录中的弹窗效果.如图所示: android中提供了QuickContactBadge来实现这一效果.这里简单演示下. 首先创建布局文件: <?xml versi ...

随机推荐

  1. CoreCLR源码探索(一) Object是什么

    .Net程序员们每天都在和Object在打交道 如果你问一个.Net程序员什么是Object,他可能会信誓旦旦的告诉你"Object还不简单吗,就是所有类型的基类" 这个答案是对的 ...

  2. 谈谈JS中的函数节流

    好吧,一直在秋招中,都没怎么写博客了...今天赶紧来补一补才行...我发现,在面试中,讲到函数节流好像可以加分,尽管这并不是特别高深的技术,下面就聊聊吧! ^_^ 备注:以下内容部分来自<Jav ...

  3. java设计模式之--单例模式

    前言:最近看完<java多线程编程核心技术>一书后,对第六章的单例模式和多线程这章颇有兴趣,我知道我看完书还是记不住多少的,写篇博客记录自己所学的只是还是很有必要的,学习贵在坚持. 单例模 ...

  4. ObserverPattern(观察者模式)

    import java.util.ArrayList; import java.util.List; /** * 观察者模式 * @author TMAC-J * 牵一发而动全身来形容观察者模式在合适 ...

  5. 在禅道中实现WORD等OFFICE文档转换为PDF进行在线浏览

    条件: 安装好禅道的服务器 能直接浏览PDF的浏览器(或通过 安装插件实现 ) 文档转换服务程序(建议部署在另一台服务器上)     实现 原理: 修改禅道的文件预览功能(OFFICE文档其使用的是下 ...

  6. How to accept Track changes in Microsoft Word 2010?

    "Track changes" is wonderful and remarkable tool of Microsoft Word 2010. The feature allow ...

  7. TFS 生成配置

      生成  

  8. 通过Mono 在 Heroku 上运行 .NET 应用

    英文原文:Running .NET on Heroku 中文原文:在 Heroku 上运行 .NET 应用 自从加入了Heroku之后,我就想在这个平台上运行.NET程序.现在我很高兴向大家宣布,我们 ...

  9. 深入浅出聊优化:从Draw Calls到GC

    前言: 刚开始写这篇文章的时候选了一个很土的题目...<Unity3D优化全解析>.因为这是一篇临时起意才写的文章,而且陈述的都是既有的事实,因而给自己“文(dou)学(bi)”加工留下的 ...

  10. 吐血大奉献,打造cnblogs最新最火辣的css3模板(IE9以下请勿入内) -- 第一版

    一直自己都想给自己的博客打造一个独一无二的皮肤,但是一直没有强劲的动力去完成这件事情.后来凭借着工作上面的需求(涉及到css3),就把自己的博客当成一个最好的试验场地.从而产生了你现在所看到的这个模板 ...