28/01/2014 MCAFEE SECURE 認證的網站

https://www.mcafeesecure.com/RatingVerify?ref=www.HongKongCupid.com

2013年6月11日 星期二

*Android App***列表之下拉刷新 *^~!

*Android App**
*列表之下拉刷新 *^~!

*Android的ListView是应用 
最广的一个组件,功能强大,
扩展性灵活(不局限于ListView
本身一个类),前面的文章有
介绍分组,拖拽,3D立体, 
游标,圆角,而今天我们要 
介绍的是另外一个扩展 
ListView:下拉刷新的ListView。
  

  下拉刷新界面最初流行
于iphone应用界面,如图:
    然后在Android中也逐渐被应用,比如微博
,资讯类。
   所以,今天要实现的结果应该也是类似的,

先贴出最终完成效果,如下图,接下来我们一
步一步实现 *
*
*1. 流程分析
    下拉刷新最主要的流程是:
    (1). 下拉,显示提示头部界面(HeaderView),
这个过程提示用户"下拉刷新"
    (2). 下拉到一定程度,超出了刷新最基本的
下拉界限,我们认为达到了刷新的条件,提示
用户可以"松手刷新"了,效果上允许用户继续
下拉 
    (3). 用户松手,可能用户下拉远远不止提示
头部界面,所以这一步,先反弹回仅显示提示
头部界面,然后提示用户"正在加载"。
    (4). 加载完成后,隐藏提示头部界面。
    示意图如下:*
*
->->
2. 实现分析
    当前我们要实现上述流程,是基于
ListView的,所以对应ListView本身的功能
我们来分析一下实现原理:
    (1). 下拉,显示提示头部界面,这个过程
提示用户"下拉刷新"
        a. 下拉的操作,首先是监听滚动,
ListView提供了onScroll()方法
        b. 与下拉类似一个动作向下飞滑,所以
ListView的scrollState有种值:SCROLL_STATE_IDLE, SCROLL_STATE_
TOUCH_SCROLL, SCROLL_STATE_FLING,
意思容易理解,而我们要下拉的触发条件是SCROLL_STATE_TOUCH_SCROLL。
判断当前的下拉操作状态,ListView提供了
public void onScrollStateChanged
(AbsListView view, int scrollState) {}。
    c. 下拉的过程中,我们可能还需要下拉
到多少的边界值处理,重写onTouchEvent
(MotionEvent ev){}方法,可依
据ACTION_DOWN,ACTION_MOVE,
ACTION_UP实现更精细的判断。
    (2). 下拉到一定程度,超出了刷新最基本
的下拉界限,我们认为达到了刷新的条件,
提示用户可以"松手刷新"了,效果上允
许用户继续下拉
        a. 达到下拉刷新界限,一般指达
到header的高度的,所以有两步,第一,
获取header的高度,第二,
当header.getBottom()>=header的高度时,
我们认为就达到了刷新界限值
        b. 继续允许用户下拉,当header完全
下拉后,默认无法继续下拉,但是可以增加
header的PaddingTop实现这种效果  
    (3). 用户松手,可能用户下拉远远不止提示
头部界面,所以这一步,先反弹回仅显示提示
头部界面,然后提示用户"正在加载"。
        a. 松手后反弹,这个不能一下子弹回去
,看上去太突然,需要一步一步柔性的弹回
去,像弹簧一样,我们可以new一个Thread循
环计算减少PaddingTop,直到PaddingTop为0,
反弹结束。
        b. 正在加载,在子线程里处理后台任务
    (4). 加载完成后,隐藏提示头部界面。
        a. 后台任务完成后,我们需要隐
藏header,setSelection(1)即实现了从第2项
开始显示,间接隐藏了header。
上面我们分析了实现过程的轮廓,接下来,
通过细节说明和代码具体实现。*

*
3. 初始化
    一切状态显示都是用HeaderView显示的,
所以我们需要一个HeaderView的layout,
使用addHeaderView方法添加到ListView中。
    同时,默认状态下,HeaderView是不显示的,
只是在下拉后才显示,所以我们需要隐藏HeaderView且---
---不影响后续的下拉显示,用setSelection(1)。
    refresh_list_header.xml布局如下:
?
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
<?xml version="1.0" encoding="utf-8"?>
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
    android:layout_width="fill_parent"
    android:layout_height="wrap_content"
    android:gravity="center">
    <ProgressBar android:id="@+id/refresh_list_header_progressbar"
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:layout_gravity="center"
        style="?android:attr/progressBarStyleSmall"
        android:visibility="gone">
    </ProgressBar>
    <ImageView android:id="@+id/refresh_list_header_pull_down"
        android:layout_width="9dip"
        android:layout_height="25dip"
        android:layout_gravity="center"
        android:src="@drawable/refresh_list_pull_down" />
    <ImageView android:id="@+id/refresh_list_header_release_up"
        android:layout_width="9dip"
        android:layout_height="25dip"
        android:layout_gravity="center"
        android:src="@drawable/refresh_list_release_up"
        android:visibility="gone" />
    <RelativeLayout android:layout_width="180dip"
        android:layout_height="wrap_content">
        <TextView android:id="@+id/refresh_list_header_text"
            android:layout_width="fill_parent"
            android:layout_height="wrap_content"
            android:gravity="center"
            android:layout_alignParentTop="true"
            android:textSize="12dip"
            android:textColor="#192F06"
            android:paddingTop="8dip"
            android:text="@string/app_list_header_refresh_down"/>
        <TextView android:id="@+id/refresh_list_header_last_update"
            android:layout_width="fill_parent"
            android:layout_height="wrap_content"
            android:gravity="center"
            android:layout_below="@id/refresh_list_header_text"
            android:textSize="12dip"
            android:textColor="#192F06"
            android:paddingBottom="8dip"
            android:text="@string/app_list_header_refresh_last_update"/>
    </RelativeLayout>
</LinearLayout>
    代码中在构造函数中添加init()方法加载如下:
?
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
private LinearLayout mHeaderLinearLayout = null;
private TextView mHeaderTextView = null;
private TextView mHeaderUpdateText = null;
private ImageView mHeaderPullDownImageView = null;
private ImageView mHeaderReleaseDownImageView = null;
private ProgressBar mHeaderProgressBar = null;
public RefreshListView(Context context) {
    this(context, null);
}
public RefreshListView(Context context, AttributeSet attrs) {
    super(context, attrs);
    init(context);
}
void init(final Context context) {
   mHeaderLinearLayout = (LinearLayout) LayoutInflater.
from(context).inflate(R.layout.refresh_list_header, null);
    addHeaderView(mHeaderLinearLayout);
    mHeaderTextView = (TextView) findViewById(R.id.refresh_
list_header_text);
    mHeaderUpdateText = (TextView) findViewById(R.id.refresh_
list_header_last_update);
    mHeaderPullDownImageView = (ImageView) findViewById(R.id.refresh_list_header_pull_down);
    mHeaderReleaseDownImageView = (ImageView) findViewById(R.id.refresh_list_header_release_up);
    mHeaderProgressBar = (ProgressBar) findViewById(R.id.refresh_
list_header_progressbar);
    setSelection(1);
}
    默认就显示完成了。*

*4. HeaderView的默认高度测量
    因为下拉到HeaderView全部显示出来,
就由提示"下拉刷新"变为"松手刷新",全部显示
的出来的测量标准
就是header.getBottom()>=header的高度。
    所以,首先我们需要测量HeaderView的
默认高度。
?
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
//因为是在构造函数里测量高度,应该先measure一下
private void measureView(View child) {
    ViewGroup.LayoutParams p = child.getLayoutParams();
    if (p == null) {
        p = new ViewGroup.LayoutParams(ViewGroup.LayoutParams.FILL_PARENT,
                ViewGroup.LayoutParams.WRAP_CONTENT);
    }
    int childWidthSpec = ViewGroup.getChildMeasureSpec(0, 0 + 0, p.width);
    int lpHeight = p.height;
    int childHeightSpec;
    if (lpHeight > 0) {
        childHeightSpec = MeasureSpec.makeMeasureSpec(lpHeight,
                MeasureSpec.EXACTLY);
    } else {
        childHeightSpec = MeasureSpec.makeMeasureSpec(0,
                MeasureSpec.UNSPECIFIED);
    }
    child.measure(childWidthSpec, childHeightSpec);
}
    然后在init的上述代码后面加上调用measureView后,
使用getMeasureHeight()方法获取header的高度:
?
1
2
3
4
5
6
private int mHeaderHeight;
void init(final Context context) {
    ... ...
    measureView(mHeaderLinearLayout);
    mHeaderHeight = mHeaderLinearLayout.getMeasuredHeight();
}
*
*5. scrollState监听记录
    scrollState有3种,使用onScrollStateChanged() 方法监听记录。
?
1
2
3
4
5
private int mCurrentScrollState;
@Override
public void onScrollStateChanged(AbsListView view, int scrollState) {  
    mCurrentScrollState = scrollState;
}
    然后即可使用mCurrentScrollState作为后面
判断的条件了。
6. 刷新状态分析
    因为一些地方需要知道我们处在正常状态
下还是进入下拉刷新状态还是松手反弹状态,
比如,
    (1). 在非正常的状态下,我们不小心飞滑了
一下(松手的瞬间容易出现这种情况),我们不能
setSelection(1)的,否则总是松手后header跳的
一下消失掉了。
    (2). 下拉后要做一个下拉效果的特殊处理,
需要用到OVER_PULL_REFRESH
(松手刷新状态下)
    (3). 松手反弹后要做一个反弹效果的特殊处理
,需要用到OVER_PULL_REFRESH和ENTER_PULL_REFRESH。
?
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
private final static int NONE_PULL_REFRESH = 0;   //正常状态
private final static int ENTER_PULL_REFRESH = 1//进入下拉刷新状态
private final static int OVER_PULL_REFRESH = 2;   //进入松手刷新状态
private final static int EXIT_PULL_REFRESH = 3;     //松手后反弹后加载状态
private int mPullRefreshState = 0;                         //记录刷新状态
@Override
public void onScroll(AbsListView view, int firstVisibleItem,
 int visibleItemCount, int totalItemCount) {
    if (mCurrentScrollState ==SCROLL_STATE_TOUCH_SCROLL
            && firstVisibleItem == 0
            && (mHeaderLinearLayout.getBottom() >=
 0 && mHeaderLinearLayout.getBottom() < mHeaderHeight)) {
        //进入且仅进入下拉刷新状态
        if (mPullRefreshState == NONE_PULL_REFRESH) {
            mPullRefreshState = ENTER_PULL_REFRESH;
        }
    } else if (mCurrentScrollState ==SCROLL_STATE_TOUCH_SCROLL
            && firstVisibleItem == 0
            && (mHeaderLinearLayout.getBottom() >=
 mHeaderHeight)) {
        //下拉达到界限,进入松手刷新状态
        if (mPullRefreshState ==
 ENTER_PULL_REFRESH || mPullRefreshState == NONE_PULL_REFRESH) {
            mPullRefreshState = OVER_PULL_REFRESH;
            //下面是进入松手刷新状态需要做的一个显示改变
            mDownY = mMoveY;//用于后面的下拉特殊效果
            mHeaderTextView.setText("松手刷新");
            mHeaderPullDownImageView.setVisibility(View.GONE);
            mHeaderReleaseDownImageView.setVisibility
(View.VISIBLE);
        }
    } else if (mCurrentScrollState ==SCROLL_STATE_TOUCH_SCROLL
 && firstVisibleItem != 0) {
        //不刷新了
        if (mPullRefreshState == ENTER_PULL_REFRESH) {
            mPullRefreshState = NONE_PULL_REFRESH;
        }
    } else if (mCurrentScrollState == SCROLL_STATE_FLING &&
 firstVisibleItem == 0) {
        //飞滑状态,不能显示出header,也不能影响正常的飞滑
        //只在正常情况下才纠正位置
        if (mPullRefreshState == NONE_PULL_REFRESH) {
            setSelection(1);
        }
    }
}
  
mPullRefreshState将是后面我们处理边界的重要变量。
6. 下拉效果的特殊处理
    所谓的特殊处理,当header完全显示后,
下拉只按下拉1/3的距离下拉,给用户一种艰难下拉,该松手的弹簧感觉。
    这个在onTouchEvent里处理比较方便:
?
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
private float mDownY;
private float mMoveY;
@Override
public boolean onTouchEvent(MotionEvent ev) {
    switch (ev.getAction()) {
        case MotionEvent.ACTION_DOWN:
            //记下按下位置
            //改变
            mDownY = ev.getY();
            break;
        case MotionEvent.ACTION_MOVE:
            //移动时手指的位置
            mMoveY = ev.getY();
            if (mPullRefreshState == OVER_PULL_REFRESH) {
                //注意下面的mDownY在onScroll的第二个else中被改变了
                mHeaderLinearLayout.
setPadding(mHeaderLinearLayout.getPaddingLeft(),
                        (int)((mMoveY - mDownY)/3), //1/3距离折扣
                        mHeaderLinearLayout.getPaddingRight(),
                        mHeaderLinearLayout.getPaddingBottom());
            }
            break;
        case MotionEvent.ACTION_UP:
            ... ...
            break;
    }
    return super.onTouchEvent(ev);
}
//重复贴出下面这段需要注意的代码
@Override
public void onScroll(AbsListView view, int firstVisibleItem,
 int visibleItemCount, int totalItemCount) {
    ... ...
    else if (mCurrentScrollState == SCROLL_STATE_TOUCH_SCROLL
            && firstVisibleItem == 0
            && (mHeaderLinearLayout.getBottom() >=
 mHeaderHeight)) {
        //下拉达到界限,进入松手刷新状态
        if (mPullRefreshState ==
 ENTER_PULL_REFRESH || mPullRefreshState == NONE_PULL_REFRESH) {  
            mPullRefreshState = OVER_PULL_REFRESH;
            mDownY = mMoveY; //为下拉1/3折扣效果记录开始位置
            mHeaderTextView.setText
("松手刷新");//显示松手刷新
            mHeaderPullDownImageView.setVisibility
(View.GONE);//隐藏"下拉刷新"
            mHeaderReleaseDownImageView.setVisibility
  (View.VISIBLE);//显示向上的箭头
        }
    }
    ... ... }
  onScroll里监听到了进入松手刷新状态,
onTouchEvent就开始在ACTION_MOVE中
处理1/3折扣问题。*

*7. 反弹效果的特殊处理
    松手后我们需要一个柔性的反弹效果,
意味着我们弹回去的过程需要分一步步走,
我的解决方案是:
    在子线程里计算PaddingTop,并减少到
原来的3/4,循环通知主线程,
直到PaddingTop小于1(这个值取一个小值,
合适即可)。
    松手后,当然是在onTouchEvent的ACTION_
UP条件下处理比较方便:
?
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
//因为涉及到handler数据处理,为方便我们定义如下常量
private final static int REFRESH_BACKING = 0;      //反弹中
private final static int REFRESH_BACED = 1;        //达到刷新界限,
反弹结束后
private final static int REFRESH_RETURN = 2;       //没有达到刷新
界限,返回
private final static int REFRESH_DONE = 3;         //加载数据结束
@Override
public boolean onTouchEvent(MotionEvent ev) {
    switch (ev.getAction()) {
        ... ...
        case MotionEvent.ACTION_UP:
            //when you action up, it will do these:
            //1. roll back util header topPadding is 0
            //2. hide the header by setSelection(1)
            if (mPullRefreshState == OVER_PULL_REFRESH ||
 mPullRefreshState == ENTER_PULL_REFRESH) {
                new Thread() {
                    public void run() {
                        Message msg;
                        while(mHeaderLinearLayout.getPaddingTop() > 1) {
                            msg = mHandler.obtainMessage();
                            msg.what = REFRESH_BACKING;
                            mHandler.sendMessage(msg);
                            try {
                                sleep(5);//慢一点反弹,
别一下子就弹回去了
                            } catch (InterruptedException e) {
                                e.printStackTrace();
                            }
                        }
                        msg = mHandler.obtainMessage();
                        if (mPullRefreshState == OVER_PULL_REFRESH) {
                            msg.what = REFRESH_BACED;//
加载数据完成,结束返回
                        } else {
                            msg.what = REFRESH_RETURN;//
未达到刷新界限,直接返回
                        }
                        mHandler.sendMessage(msg);
                    };
                }.start();
            }
            break;
    }
    return super.onTouchEvent(ev);
}
private Handler mHandler = new Handler(){
    @Override
    public void handleMessage(Message msg) {
        switch (msg.what) {
        case REFRESH_BACKING:
            mHeaderLinearLayout.setPadding
(mHeaderLinearLayout.getPaddingLeft(),
                    (int) (mHeaderLinearLayout.
getPaddingTop()*0.75f),
                    mHeaderLinearLayout.getPaddingRight(),
                    mHeaderLinearLayout.getPaddingBottom());
            break;
        case REFRESH_BACED:
            mHeaderTextView.setText("正在加载...");
            mHeaderProgressBar.setVisibility(View.VISIBLE);
            mHeaderPullDownImageView.setVisibility(View.GONE);
            mHeaderReleaseDownImageView.setVisibility(View.GONE);
            mPullRefreshState = EXIT_PULL_REFRESH;
            new Thread() {
                public void run() {
                    sleep(2000);//处理后台加载数据
                    Message msg = mHandler.obtainMessage();
                    msg.what = REFRESH_DONE;
                    //通知主线程加载数据完成
                    mHandler.sendMessage(msg);
                };
            }.start();
            break;
        case REFRESH_RETURN:
            //未达到刷新界限,返回
            mHeaderTextView.setText("下拉刷新");
            mHeaderProgressBar.setVisibility(View.INVISIBLE);
            mHeaderPullDownImageView.setVisibility(View.VISIBLE);
            mHeaderReleaseDownImageView.setVisibility(View.GONE);
            mHeaderLinearLayout.setPadding(mHeaderLinearLayout.
getPaddingLeft(),
                    0,
                    mHeaderLinearLayout.getPaddingRight(),
                    mHeaderLinearLayout.getPaddingBottom());
            mPullRefreshState = NONE_PULL_REFRESH;
            setSelection(1);
            break;
        case REFRESH_DONE:
            //刷新结束后,恢复原始默认状态
            mHeaderTextView.setText("下拉刷新");
            mHeaderProgressBar.setVisibility(View.INVISIBLE);
            mHeaderPullDownImageView.setVisibility(View.VISIBLE);
            mHeaderReleaseDownImageView.setVisibility(View.GONE);
            mHeaderUpdateText.setText(getContext().getString(R.string.app_list_header_refresh_last_update, 
                    mSimpleDateFormat.format(new Date())));
            mHeaderLinearLayout.setPadding
(mHeaderLinearLayout.getPaddingLeft(),
                    0,
                    mHeaderLinearLayout.getPaddingRight(),
                    mHeaderLinearLayout.getPaddingBottom());
            mPullRefreshState = NONE_PULL_REFRESH;
            setSelection(1);
            break;
        default:
            break;
        }
    }
};
   
为了一下子看的明确,我把效果中的数据
处理代码也贴出来了。*

*
8. 切入数据加载过程
    上面数据后台处理我们用sleep(2000)来处理,实际处理中,
作为公共组件,我们也不好把具体代码直接写在这里,
我们需要一个更灵活的分离:
    (1). 定义接口
    (2). 注入接口
?
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
//定义接口
public interface RefreshListener {
    Object refreshing();                //加载数据
    void refreshed(Object obj);    //外部可扩展加载完成后的操作
}
//注入接口
private Object mRefreshObject = null; //传值
private RefreshListener mRefreshListener = null;
public void setOnRefreshListener(RefreshListener refreshListener) {
    this.mRefreshListener = refreshListener;
}
//我们需要重写上面的mHandler如下代码
case REFRESH_BACED:
    ... ...
    new Thread() {
        public void run() {
            if (mRefreshListener != null) {
                mRefreshObject = mRefreshListener.refreshing();
            }
            Message msg = mHandler.obtainMessage();
            msg.what = REFRESH_DONE;
            mHandler.sendMessage(msg);
        };
    }.start();
    break;
case REFRESH_DONE:
    ... ...
    mPullRefreshState = NONE_PULL_REFRESH;
    setSelection(1);
    if (mRefreshListener != null) {
        mRefreshListener.refreshed(mRefreshObject);
    }
    break;
    在其他地方我们就可以不修改这个listview组件的代码,使用如下:
?
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
public xxx implements RefreshListener{
  
@Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        //类似如下
        ((RefreshListView) listView).setOnRefreshListener(this);
    }
  
    @Override
    public Object refreshing() {
        String result = null;
        //result = FileUtils.readTextFile(file);
        return result;
    }
  
    @Override
    public void refreshed(Object obj) {
        if (obj != null) {
           //扩展操作
        }
    };
}
  很方便了。*

*9. 扩展"更多"功能
    下拉刷新之外,我们也可以通过相同
方法使用FooterView切入底部"更多"过程,
这里我就不详细说明了
10. 源码
    上面的每段代码都看做是"零部件",需要
组合一下。
    因为我们上面实现了下拉刷新,还增加
了"更多"功能,我们直接命名这个类为
RefreshListView吧:
?
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
package com.tianxia.lib.baseworld.widget;
  
import java.text.SimpleDateFormat;
import java.util.Date;
  
import android.content.Context;
import android.os.Handler;
import android.os.Message;
import android.util.AttributeSet;
import android.view.LayoutInflater;
import android.view.MotionEvent;
import android.view.View;
import android.view.ViewGroup;
import android.widget.AbsListView;
import android.widget.AbsListView.OnScrollListener;
import android.widget.ImageView;
import android.widget.LinearLayout;
import android.widget.ListAdapter;
import android.widget.ListView;
import android.widget.ProgressBar;
import android.widget.TextView;
  
import com.tianxia.lib.baseworld.R;
  
/**
 * 下拉刷新,底部更多
 *
 */
public class RefreshListView extends ListView
implements OnScrollListener{
  
    private float mDownY;
    private float mMoveY;
  
    private int mHeaderHeight;
  
    private int mCurrentScrollState;
  
    private final static int NONE_PULL_REFRESH = 0;   
//正常状态
    private final static int ENTER_PULL_REFRESH = 1;  
//进入下拉刷新状态
    private final static int OVER_PULL_REFRESH = 2;   
//进入松手刷新状态
    private final static int EXIT_PULL_REFRESH = 3;   
//松手后反弹和加载状态
    private int mPullRefreshState = 0;                
 //记录刷新状态
  
    private final static int REFRESH_BACKING = 0;     
//反弹中
    private final static int REFRESH_BACED = 1;       
//达到刷新界限,反弹结束后
    private final static int REFRESH_RETURN = 2;      
 //没有达到刷新界限,返回
    private final static int REFRESH_DONE = 3;        
//加载数据结束
  
    private LinearLayout mHeaderLinearLayout = null;
    private LinearLayout mFooterLinearLayout = null;
    private TextView mHeaderTextView = null;
    private TextView mHeaderUpdateText = null;
    private ImageView mHeaderPullDownImageView = null;
    private ImageView mHeaderReleaseDownImageView = null;
    private ProgressBar mHeaderProgressBar = null;
    private TextView mFooterTextView = null;
    private ProgressBar mFooterProgressBar = null;
  
    private SimpleDateFormat mSimpleDateFormat;
  
    private Object mRefreshObject = null;
    private RefreshListener mRefreshListener = null;
    public void setOnRefreshListener
(RefreshListener refreshListener) {
        this.mRefreshListener = refreshListener;
    }
  
    public RefreshListView(Context context) {
        this(context, null);
    }
  
    public RefreshListView(Context context,
 AttributeSet attrs) {
        super(context, attrs);
        init(context);
    }
  
    void init(final Context context) {
        mHeaderLinearLayout = (LinearLayout)
 LayoutInflater.from(context).inflate(R.layout.
refresh_list_header, null);
        addHeaderView(mHeaderLinearLayout);
        mHeaderTextView = (TextView) findViewById
(R.id.refresh_list_header_text);
        mHeaderUpdateText = (TextView) findViewById(R.id.refresh_list_header_last_update);
        mHeaderPullDownImageView = (ImageView) findViewById(R.id.refresh_list_header_pull_down);
        mHeaderReleaseDownImageView = (ImageView) findViewById(R.id.refresh_list_header_release_up);
        mHeaderProgressBar = (ProgressBar) findViewById(R.id.refresh_list_header_progressbar);
  
        mFooterLinearLayout = (LinearLayout)
 LayoutInflater.from(context).inflate(R.layout.
refresh_list_footer, null);
        addFooterView(mFooterLinearLayout);
        mFooterProgressBar = (ProgressBar) findViewById(R.id.refresh_list_footer_progressbar);
        mFooterTextView = (TextView)
 mFooterLinearLayout.findViewById(R.id.refresh_list_footer_text);
        mFooterLinearLayout.setOnClickListener
(new OnClickListener() {
            @Override
            public void onClick(View v) {
                if (context.getString(R.string.
app_list_footer_more).equals(mFooterTextView.getText())) {
                    mFooterTextView.setText
(R.string.app_list_footer_loading);
                    mFooterProgressBar.setVisibility(View.VISIBLE);
                    if (mRefreshListener != null) {
                        mRefreshListener.more();
                    }
                }
            }
        });
  
        setSelection(1);
        setOnScrollListener(this);
        measureView(mHeaderLinearLayout);
        mHeaderHeight = mHeaderLinearLayout.getMeasuredHeight();
  
        mSimpleDateFormat = new SimpleDateFormat
("yyyy-MM-dd hh:mm");
        mHeaderUpdateText.setText(context.getString(R.string.app_list_header_refresh_last_update,
 mSimpleDateFormat.format(new Date())));
    }
  
    @Override
    public boolean onTouchEvent(MotionEvent ev) {
        switch (ev.getAction()) {
            case MotionEvent.ACTION_DOWN:
                mDownY = ev.getY();
                break;
            case MotionEvent.ACTION_MOVE:
                mMoveY = ev.getY();
                if (mPullRefreshState == OVER_PULL_REFRESH) {
                    mHeaderLinearLayout.setPadding
(mHeaderLinearLayout.getPaddingLeft(),
                            (int)((mMoveY - mDownY)/3),
                            mHeaderLinearLayout.getPaddingRight(),
                            mHeaderLinearLayout.getPaddingBottom());
                }
                break;
            case MotionEvent.ACTION_UP:
                //when you action up, it will do these:
                //1. roll back util header topPadding is 0
                //2. hide the header by setSelection(1)
                if (mPullRefreshState == OVER_PULL_REFRESH ||
 mPullRefreshState == ENTER_PULL_REFRESH) {
                    new Thread() {
                        public void run() {
                            Message msg;
                            while(mHeaderLinearLayout.
getPaddingTop() > 1) {
                                msg = mHandler.obtainMessage();
                                msg.what = REFRESH_BACKING;
                                mHandler.sendMessage(msg);
                                try {
                                    sleep(5);
                                } catch
(InterruptedException e) {
                                    e.printStackTrace();
                                }
                            }
                            msg = mHandler.obtainMessage();
                            if (mPullRefreshState ==
 OVER_PULL_REFRESH) {
                                msg.what = REFRESH_BACED;
                            } else {
                                msg.what = REFRESH_RETURN;
                            }
                            mHandler.sendMessage(msg);
                        };
                    }.start();
                }
                break;
        }
        return super.onTouchEvent(ev);
    }
  
    @Override
    public void onScroll(AbsListView view,
 int firstVisibleItem, int visibleItemCount, int totalItemCount) {
        if (mCurrentScrollState == SCROLL_STATE_TOUCH_SCROLL
                && firstVisibleItem == 0
                && (mHeaderLinearLayout.getBottom() >=
 0 && mHeaderLinearLayout.getBottom() < mHeaderHeight)) {
            //进入且仅进入下拉刷新状态
            if (mPullRefreshState == NONE_PULL_REFRESH) {
                mPullRefreshState = ENTER_PULL_REFRESH;
            }
        } else if (mCurrentScrollState ==
 SCROLL_STATE_TOUCH_SCROLL
                && firstVisibleItem == 0
                && (mHeaderLinearLayout.getBottom() >=
 mHeaderHeight)) {
            //下拉达到界限,进入松手刷新状态
            if (mPullRefreshState ==
 ENTER_PULL_REFRESH || mPullRefreshState == NONE_PULL_REFRESH) {
                mPullRefreshState = OVER_PULL_REFRESH;
                mDownY = mMoveY; //为下拉1/3折扣效果记录开始位置
                mHeaderTextView.setText("松手刷新");//显示松手刷新
                mHeaderPullDownImageView.setVisibility
(View.GONE);//隐藏"下拉刷新"
                mHeaderReleaseDownImageView.setVisibility
(View.VISIBLE);//显示向上的箭头
            }
        } else if (mCurrentScrollState ==
 SCROLL_STATE_TOUCH_SCROLL && firstVisibleItem != 0) {
            //不刷新了
            if (mPullRefreshState == ENTER_PULL_REFRESH) {
                mPullRefreshState = NONE_PULL_REFRESH;
            }
        } else if (mCurrentScrollState ==
 SCROLL_STATE_FLING && firstVisibleItem == 0) {
            //飞滑状态,不能显示出header,也不能影响正常的飞滑
            //只在正常情况下才纠正位置
            if (mPullRefreshState == NONE_PULL_REFRESH) {
                setSelection(1);
            }
        }
    }
  
    @Override
    public void onScrollStateChanged
(AbsListView view, int scrollState) {
        mCurrentScrollState = scrollState;
    }
  
    @Override
    public void setAdapter(ListAdapter adapter) {
        super.setAdapter(adapter);
        setSelection(1);
    }
  
    private void measureView(View child) {
        ViewGroup.LayoutParams p = child.getLayoutParams();
        if (p == null) {
            p = new ViewGroup.LayoutParams
(ViewGroup.LayoutParams.FILL_PARENT,
                    ViewGroup.LayoutParams.WRAP_CONTENT);
        }
  
        int childWidthSpec = ViewGroup.getChildMeasureSpec
(0, 0 + 0, p.width);
        int lpHeight = p.height;
        int childHeightSpec;
        if (lpHeight > 0) {
            childHeightSpec =
MeasureSpec.makeMeasureSpec(lpHeight,
                    MeasureSpec.EXACTLY);
        } else {
            childHeightSpec = MeasureSpec.makeMeasureSpec(0,
                    MeasureSpec.UNSPECIFIED);
        }
        child.measure(childWidthSpec, childHeightSpec);
    }
  
    private Handler mHandler = new Handler(){
        @Override
        public void handleMessage(Message msg) {
            switch (msg.what) {
            case REFRESH_BACKING:
                mHeaderLinearLayout.setPadding
(mHeaderLinearLayout.getPaddingLeft(),
                        (int) (mHeaderLinearLayout.
getPaddingTop()*0.75f),
                        mHeaderLinearLayout.getPaddingRight(),
                        mHeaderLinearLayout.getPaddingBottom());
                break;
            case REFRESH_BACED:
                mHeaderTextView.setText("正在加载...");
                mHeaderProgressBar.setVisibility(View.VISIBLE);
                mHeaderPullDownImageView.setVisibility(View.GONE);
                mHeaderReleaseDownImageView.setVisibility(View.GONE);
                mPullRefreshState = EXIT_PULL_REFRESH;
                new Thread() {
                    public void run() {
                        if (mRefreshListener != null) {
                            mRefreshObject =
 mRefreshListener.refreshing();
                        }
                        Message msg = mHandler.obtainMessage();
                        msg.what = REFRESH_DONE;
                        mHandler.sendMessage(msg);
                    };
                }.start();
                break;
            case REFRESH_RETURN:
                mHeaderTextView.setText("下拉刷新");
                mHeaderProgressBar.setVisibility(View.INVISIBLE);
                mHeaderPullDownImageView.setVisibility(View.VISIBLE);
                mHeaderReleaseDownImageView.setVisibility(View.GONE);
                mHeaderLinearLayout.setPadding
(mHeaderLinearLayout.getPaddingLeft(),
                        0,
                        mHeaderLinearLayout.getPaddingRight(),
                        mHeaderLinearLayout.getPaddingBottom());
                mPullRefreshState = NONE_PULL_REFRESH;
                setSelection(1);
                break;
            case REFRESH_DONE:
                mHeaderTextView.setText("下拉刷新");
                mHeaderProgressBar.setVisibility(View.INVISIBLE);
                mHeaderPullDownImageView.setVisibility(View.VISIBLE);
                mHeaderReleaseDownImageView.setVisibility(View.GONE);
                mHeaderUpdateText.setText(getContext().getString(R.string.app_list_header_refresh_last_update,
                        mSimpleDateFormat.format(new Date())));
                mHeaderLinearLayout.setPadding
(mHeaderLinearLayout.getPaddingLeft(),
                        0,
                        mHeaderLinearLayout.getPaddingRight(),
                        mHeaderLinearLayout.getPaddingBottom());
                mPullRefreshState = NONE_PULL_REFRESH;
                setSelection(1);
                if (mRefreshListener != null) {
                    mRefreshListener.refreshed(mRefreshObject);
                }
                break;
            default:
                break;
            }
        }
    };
    public interface RefreshListener {
        Object refreshing();
        void refreshed(Object obj);
        void more();
    }
  
    public void finishFootView() {
        mFooterProgressBar.setVisibility(View.GONE);
        mFooterTextView.setText(R.string.app_list_footer_more);
    }
  
    public void addFootView() {
        if (getFooterViewsCount() == 0) {
            addFooterView(mFooterLinearLayout);
        }
    }
  
    public void removeFootView() {
        removeFooterView(mFooterLinearLayout);
    }
}
**
&这个只是一个原型,无论代码风格和逻辑处理&
&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&

2013年6月10日 星期一

*Android设计模式--之策略模式 *(建造者模式)&*进行Android单元测试***~!

** *Android设计模式--*
*之策略模式 **~!*(建造者模式)&**
进行Android单元测试***~!

* 1 public abstract class Animation implements Cloneable { 2     Interpolator mInterpolator; 3     // 通过set方法注入    
4     public void
setInterpolator(Interpolator i) { 5          mInterpolator = i; 6      } 7   8     public boolean getTransformation(long currentTime,

Transformation outTransformation) { 9         // ... ... 10         // 具体调用
11         final float interpolatedTime =
mInterpolator.getInterpolation

(normalizedTime); 12         applyTransformation(interpolatedTime, outTransformation); 13        // ... ...
14
    } 15   16      // 缺省实现,是个小技巧,顺便提下,这个不是重点
17      protected void
ensureInterpolator() { 18          if (mInterpolator == null) { 19              mInterpolator = new AccelerateDecelerateInterpolator(); 20          } 21      } 22   23 }                                  *


*策略模式其实特别简单(听到这句话,
大家是不是心里一下子放松了?)。
比如排序,官方告诉大家我这里有一个
排序的接口ISort的sort()方法,然后民间
各尽其能,实现这个
排序的方法:冒泡,快速,堆等等。
这些方法就是“不同的策略”。
然后,某个模块下,需要一个排序方法,
但是暂时不能指定具体的sort方法
(出于扩展的考虑),就需 要使
用ISort接口了。
最后,具体什么场景下,传入什么具体的
sort方法,实现灵活的排序。
这就是策略模式!
下面,我们分析Android中的动画是如何使
用策略模式的。
1. 意图
定义一系列的算法,把它们一个个封装起来,
并且使它们可互相替换。
策略模式使得算法可独立于使用它的
客户而变化。
2. 结构图和代码
Animation不同动画的实现,主要是依
靠Interpolator的不同实现而变。
  
定义接口Interpolator:
复制代码
 1 package android.animation; 
 2   
 3 /** 
 4  * A time interpolator defines the rate 
of change of an animation. This allows 
animations 
 5  * to have non-linear motion, such as 
acceleration and deceleration. 
 6  */ 7 public interface Interpolator { 
 8   
 9     /** 
10      * Maps a value representing the 
elapsed fraction of an animation to a value 
that represents 
11      * the interpolated fraction. This 
interpolated value is then multiplied by 
the change in 
12      * value of an animation to derive 
the animated value at the current elapsed 
animation time. 
13      * 
14      * @param input A value between 0 
and 1.0 indicating our current point 
15      *        in the animation where 0 
represents the start and 1.0 represents 
16      *        the end 
17      * @return The interpolation value. 
This value can be more than 1.0 for 
18      *         interpolators which 
overshoot their targets, or less than 0 for 
19      *         interpolators that 
undershoot their targets. 
20      */21     float getInterpolation(float input); 
22 }
复制代码
我们以AccelerateInterpolator为例,实现
具体的策略,代码如下:
复制代码
 1 package android.view.animation; 
 2   
 3 import android.content.Context; 
 4 import android.content.res.TypedArray; 
 5 import android.util.AttributeSet; 
 6   
 7 /** 
 8  * An interpolator where the rate of 
change starts out slowly and 
 9  * and then accelerates. 
10  * 
11  */12 public class AccelerateInterpolator 
implements Interpolator { 
13     private final float mFactor; 
14     private final double mDoubleFactor; 
15   
16     public AccelerateInterpolator() { 
17         mFactor = 1.0f; 
18         mDoubleFactor = 2.0; 
19     } 
20   
21     /** 
22      * Constructor 
23      * 
24      * @param factor Degree to which the 
animation should be eased. Seting 
25      *        factor to 1.0f produces a 
y=x^2 parabola. Increasing factor above 
26      *        1.0f  exaggerates the 
ease-in effect (i.e., it starts even 
27      *        slower and ends evens faster) 
28      */29     public AccelerateInterpolator 
(float factor) { 
30         mFactor = factor; 
31         mDoubleFactor = 2 * mFactor; 
32     } 
33   
34     public AccelerateInterpolator 
(Context context, AttributeSet attrs) { 
35         TypedArray a = 
36             context.obtainStyledAttributes 
(attrs, com.android.internal.R.styleable. 
AccelerateInterpolator); 
37   
38         mFactor = a.getFloat 
(com.android.internal.R.styleable.
AccelerateInterpolator_factor, 1.0f); 
39         mDoubleFactor = 2 * mFactor; 
40   
41         a.recycle(); 
42     } 
43   
44     public float getInterpolation 
(float input) { 
45         if (mFactor == 1.0f) { 
46             return input * input; 
47         } else { 
48             return (float)Math.pow(input,
 mDoubleFactor); 
49         } 
50     } 
51 }
复制代码
其他的Interpolator实现在此不列举了。
如何在Animation模块实现不同的动画呢?
在这里我想提一个应用很广的概念:
依赖注入。
在Animation模块里实现不同的动画,就是
需要我们把各个Interpolator以父类或者接
口的形式注入进去。
注入的方法一般是构造函数,set方法,
注释等等。
我们看看animation类是怎么做的:
策略模式其实就是多态的一个淋漓精
致的体现。
3. 效果
(1).行为型模式
(2).消除了一些if...else...的条件语句
(3).客户可以对实现进行选择,但是客户必须
要了解这个不同策略的实现(这句话好像是
废话,总而言之,客户需要学习成本)
(4).代码注释中提到了缺省实现,可以让客
户不了解策略,也能实现默认的策略
(5).注入的方式有多种:构造函数,set方法
,注释。配置解析等等  *

{{*如何进行Android单元测试*
*
  1. Menifest.xml中加入:
    <application>中加入:
    <uses-library android:name="android.test.runner" />
    <application>外面加入:
    <uses-permission android:name="android.
  2. permission.RUN_
  3. INSTRUMENTATION" />
    <instrumentation android:name="android.test.
  4. InstrumentationTestRunner" android:targetPackage="name.feisky.android.test"
    android:label="Test for my app"/>
  5. 编写单元测试代码:必须继承自AndroidTestCase
    package name.feisky.android.test;
     
    import android.test.AndroidTestCase;
    import junit.framework.Assert;
     
    public class MyTest extends AndroidTestCase {
    private static final String Tag="MyTest";
     
    public void testSave() throws Throwable
    {
    int i=4+8;
    Assert.assertEquals(5,i);
    }
     
    public void testSomethingElse() throws Throwable {
    Assert.assertTrue(1 + 1 == 12);
    }
     
    }

执行测IntelliJ中:
      

  1.  
     
    eclipse中:右键 run as Android JUnit Test
    命令行工具:
    adb shell am instrument -w name.feisky.
  2. android.test/android.test.
  3. InstrumentationTestRunner
       
也可以新建一个测试项目进行测试
 
  1. New > Project > Android > Android Test Project.

  1. 添加测试用例类
    添加新类,基类设置
  2. android.test.ActivityInstrumentationTestCase2<HelloAndroid>
  3. 添加构造函数
    添加setUp()方法,这个方法在所有的测试之前进行
  4. 变量和测试环境的初始化。
    @Override
        protected void setUp() throws Exception {
            super.setUp();
            mActivity = this.getActivity();
            mView = (TextView) mActivity.findViewById(com.example.helloandroid.R.id.textview);
            resourceString = mActivity.getString(com.example.helloandroid.R.string.hello);
        }
  5. 添加testPreconditions()方法,检查初始化环境,
  6. 只执行一次
    public void testPreconditions() {
          assertNotNull(mView);
        }
  7. 添加单元测试
    public void testText() {
          assertEquals(resourceString,(String)mView.getText());
        }
  8. 测试 Run As... > Android JUnit Test
  9. *
    {{*Android设计模式--- 
      (之生成器模式  (建造者模式)*
    *
    建造者模式把构造和表示分离开,根据客
    户需求生产一个相应的对象。
    本来呢,我们根据Builder接口实现不同
    的具体的ConcreteBuilder,就可生产
    不同的对象了。
    但是,下面例子的只有一个Builder,
    所以也没有接口Builder,也没有其他的ConcreteBuilder。
    但是我今天要讲的例子太简单,简单到
    都不觉得是建造者模式,但是又有建造
    者模式的感觉。
    1. 意图
    将一个复杂对象的构建和它的表示分离,
    使得同样的创建过程可以创建不同的表示。
    2.结构图和代码
    android中大量的使用对话框组件,
    它的调用方法就是构建,拼接,表示。

    Builder通过setTitle(),setMessage(),
    setIcon()等方法在create()中构造出一个AlertDialogInstance对象。
    然后客户可以把把AlertDialogInstance
    对象show出去。
    针对这个例子,我有必要通过扩展一下,
    以正建造者模式之名。
    如何扩展?
    (1).创建其他的ConcreteBuilder
    (2).创建其他的ElseDialog
    (3).第1,2步决定了我们最好创建一个
    抽象的Builder
    (4).创建一个Context,把builder注入
    进去,生产不同的Dialog
    (或者其他UI)对象。
    3.效果
    (1).创建型模式
    (2).可以生产不同的Dialog,也可以生产
    其他的UI,生产的Product可以是差别很
    大的,所以也没必要为Product定义
    抽象父类。
    *                    *
          &&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&

        ***Linux+Apache+PHP**+MySQL服务器环境(CentOS篇) *

        ***Linux+Apache+PHP**
        +MySQL服务器环境(CentOS篇) *

        *CentOS(Community ENTerprise Operating System)是Linux发行版
        之一,它是来自于Red Hat Enterprise Linux依照开放源代码规定释出
        的源代码所编译而成。CentOS是架构LAMP
        (Linux+Apache+PHP+MySQL)的理想操作系统,具有较高的稳定性。
        本文以CentOS为例介绍linux系统下yum安装Apache+PHP+MySQL的方法*

        *2.准备工作

        2.1.登录服务器

        登录远程服务器,如果您的服务器在本地
        并安装有桌面环境,也可以直接打开服务
        器桌面,进入终端管理器。
        在windows下Putty是一款出色的liunx远程登
        录工具,打开界面如下
         
         在图中黄色框中填入服务器ip,点击打开,
        之后提示输入用户名和密码,验证成功后完
        成登录服务器。
         2.2.配置源和更新系统软件

        2.2.1.定义非官方yum库

        官方提供的软件是当前的稳定版本,但不是
        最新版本,为了更好的运行dedecms,我们需要
        一些非官方的yum源。
        /RPM-GPG-KEY-jlitka
        vi /etc/yum.repos.d/utterramblings.repo
        添加以下内容
        [utterramblings]
        name=Jason's Utter Ramblings Repo
        baseurl=http://www.jasonlitka.com/media
        /EL$releasever/$basearch/
        enabled=1
        gpgcheck=1
        gpgkey=http://www.jasonlitka.com/media/
        RPM-GPG-KEY-jlitka
        [注]vim编辑器修改文件的方法请自行百度之
         2.2.2.更新系统
        yum update
        yum upgrade
         2.2.3.检查系统软件
        检查当前系统是否已经安装了
        apache 和 mysql
        yum list installed | grep httpd
        rpm -qa | grep httpd
        yum list installed | grep mysql
        rpm -qa | grep mysql
        如果已经存在,请先卸载

        2.3.创建目录

        在这里我们约定各数据存放目录如下
        网站数据   /www/htdocs
        日志目录   /www/log
        数据库文件 /www/mysql
        运行以下命令完成目录创建
        mkdir /www/mysql
        mkdir /www/htdocs
        mkdir /www/log/php
        mkdir /www/log/mysql
        添加apache和mysql用户
        useradd -s /sbin/nologin -M apache
        useradd -s /sbin/nologin -M mysql
        创建了目录之后还需要改变目录属主和
        用户组,apache和mysql才能完成读写操作
        chown -R apache:apache /www/htdocs
        chown -R mysql:mysql /www/mysql
        chown -R apache:apache /www/log/php
        chown -R mysql:mysql /www/log/mysql

        3.安装与配置*

        *3.1. yum 安装 Apache + PHP + MySQL
        yum -y install gcc gcc-c++ autoconf make 
         aclocal libtool expat-devel libxml2-devel 
        httpd php php-devel mysql mysql-server 
        mysql-devel libevent libevent-devel mageMagick 
        ImageMagick-devel php-mysql mod_ssl mod_perl mod_auth_mysql php-mcrypt php-gd php-xml 
        php-mcrypt php-mbstring php-ldap php-pear 
         php-xmlrpc php-pecl-memcache mysql-
        connector-odbc libdbi-dbd-mysql php-
        eaccelerator*
        *3.2.配置php
        vi /etc/php.ini
        请根据自己的情况修改,这里只列出部分
        需要注意的设置
        post_max_size = 32M
        memory_limit = 256M
        allow_url_fopen = On
        upload_max_filesize = 32M
        upload_tmp_dir = /var/tmp
        log_errors = On
        error_reporting = E_ALL & ~E_NOTICE |
        E_STRICT
        display_errors = Off
        error_log = /www/log/php/php_error.log
        magic_quotes_gpc = On*
        *3.3. 配置apache
        先给默认配置文件做个备份
        cp /etc/httpd/conf/httpd.conf /etc/httpd/
        conf/httpd.conf.bak
        编辑配置
        vi /etc/httpd/conf/httpd.conf
        根据自己的需要进行修改
        DocumentRoot "/www/htdocs"  # 修改为
        默认网站主目录
        NameVirtualHost *:80
        如果安装GBK版本dedecms出现乱码,可能
        需要注释掉这个参数
        #AddDefaultCharset UTF-8
        启动apache服务
        service httpd start
        检查apache是否正常,打开浏览器,输入 
        http://localhost,如果看到默认apache test page
        页面,即表示成功启动。*
        *3.4.配置mysql
        系统已经自动安装好了mysql,但我们需要
        做些简单的修改和优化,配置数据库文件
        和日志存放位置,才能启动。
        在 /usr/share/mysql/目录下有多个my-开头
        的cnf文件,我们可以针对我们自己的情况
        选择一个使用,我们这里选择my-medium.cnf
        把它复制到/etc目录里
        cp /usr/share/mysql/my-medium.cnf /etc/my.cnf
        编辑
        vi /etc/my.cnf
        修改以下配置,在原有的基础上修改,
        没有的选项就添加
        [client]
        port = 3306
        socket = /var/lib/mysql/mysql.sock
        default-character-set = utf8
        [mysqld]
        user = mysql
        datadir = /www/mysql
        log-error = /www/log/mysql/mysql_error.log
        log-bin=/www/log/mysql/mysql-bin
        expire_logs_days=7
        character-set-server = utf8
        --skip-external-locking
        启动mysql
        service mysqld start
        系统会自动安装初始数据库,然后启动,
        如果有错误,请查看/www/log/mysql
        /mysql_error.log里的错误信息
        设置mysql root用户密码
        mysql
        UPDATE mysql.user SET password =
        PASSWORD('你的密码') WHERE user = 'root';
        FLUSH PRIVILEGES;
        exit;
        +++++++++++++++++
        删除mysql一些无用的账号信息,root mysql只
        允许本地链接 安全设置。
        +++++++++++++++++ *
        *4.优化设置*
        *

        4.1.安全设置,禁用 PHP 的敏感函数

        vi /etc/php.ini
        取消disable_functions前的#,改为
        disable_functions = exec,shell_exec,system,popen,escapeshellcmd,
        escapeshellarg,gzuncompress,proc_open,proc_
        get_status,show_source,gzinflate

        4.2.开启gzip压缩

        gzip压缩可以减少服务器流量,但也会增
        加cpu资源消耗,是否要开启你需要
        根据情况决定
        vi /etc/httpd/conf/httpd.conf
        LoadModule deflate_module modules/
        mod_deflate.so
        <IfModule mod_deflate.c>
        DeflateCompressionLevel 6
        AddOutputFilterByType DEFLATE text/
        html text/plain text/xml application/x-httpd-php
        AddOutputFilter DEFLATE js css
        </IfModule>          *
        *5.建立网站*
        *
        创建网站目录
        mkdir /www/htdocs/demo
        更改以下网站目录的属主和属组
        chown -R apache:apache /www/htdocs/demo
        配置虚拟主机配置
        vi /etc/httpd/conf/httpd.conf
        在最后添加
        <VirtualHost *:80>
        ServerName hacktea8.com
        ServerAlias www.hacktea8.com
        DocumentRoot /www/htdocs/demo
        </VirtualHost>
        重启apache 
        service httpd restart
        +++++++++++++++
        本地测试 需要将域名与IP对应  写在本
        地hosts文件
        +++++++++++++++
        创建数据库
        mysql -u root -p
        create database demo; #demo为要创建的
        数据库名
        exit;
        环境搭建已经全部完成了,下面您只需要
        把Web网站程序上传到/www/htdocs/demo,
        并把您的域名解析到您的服务器ip,即可
        完成Web网站的安装,本教程到此结束。*
        &&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&