Coding Our Worldhttp://www.xycoding.com/2017-05-05T17:25:00+08:00Android TextView富文本自定义标签2017-05-05T17:25:00+08:002017-05-05T17:25:00+08:00xymelontag:www.xycoding.com,2017-05-05:/articles/2017/05/05/android-richtext/<p>在Android开发中,经常会遇到UI同学的“奇思妙想”,比如同一行文本需要展示不同的样式(字体、颜色、背景、点击事件等),通常的做法当然是利用<code>TextView Spannable</code>针对不同的文本添加不同的span,而这个过程非常繁琐,所以利用空闲时间,写了个自定义富文本标签的开源库,欢迎大家拍砖并提出建议。</p>
<p>在Android开发中,经常会遇到UI同学的“奇思妙想”,比如同一行文本需要展示不同的样式(字体、颜色、背景、点击事件等),通常的做法当然是利用<code>TextView Spannable</code>针对不同的文本添加不同的span,而这个过程非常繁琐,所以利用空闲时间,写了个自定义富文本标签的开源库,欢迎大家拍砖并提出建议。</p>
<h2>特性</h2>
<ol>
<li>超链接点击 hyperlink click event (with pressed text and background color)</li>
<li>文本点击 click event (with pressed text and background color)</li>
<li>文本背景色 text background color</li>
<li>文本前景色 text foreground color</li>
<li>文本大小 text size</li>
<li>字体样式 text style (bold, italic)</li>
<li>字体 text font</li>
</ol>
<p>当然,本开源库扩展性非常强,你可以利用提供的api <code>addTypeSpan</code>添加自定义span。</p>
<p><img alt="image" src="/images/RichText.gif"></p>
<p>动态图中, <code>Rich Text Format</code>为超链接,点击回调会返回当前链接,普通点击会返回当前word和word中心坐标;<code>Microsoft Corporation</code>指定了不同字体;更多特性你可自行添加。</p>
<h2>安装</h2>
<p>In your project level <code>build.gradle</code> :</p>
<div class="highlight"><pre><span></span><span class="n">allprojects</span> <span class="o">{</span>
<span class="n">repositories</span> <span class="o">{</span>
<span class="o">...</span>
<span class="n">maven</span> <span class="o">{</span> <span class="n">url</span> <span class="s">"https://jitpack.io"</span> <span class="o">}</span>
<span class="o">}</span>
<span class="o">}</span>
</pre></div>
<p>In your app level <code>build.gradle</code> :</p>
<div class="highlight"><pre><span></span><span class="n">dependencies</span> <span class="o">{</span>
<span class="n">compile</span> <span class="err">'</span><span class="n">com</span><span class="o">.</span><span class="na">github</span><span class="o">.</span><span class="na">xymelon</span><span class="o">:</span><span class="n">richtext</span><span class="o">:</span><span class="mf">1.0.4</span><span class="err">'</span>
<span class="o">}</span>
</pre></div>
<h2>Demo</h2>
<div class="highlight"><pre><span></span><span class="n">TextView</span> <span class="n">textView</span> <span class="o">=</span> <span class="o">(</span><span class="n">TextView</span><span class="o">)</span> <span class="n">findViewById</span><span class="o">(</span><span class="n">R</span><span class="o">.</span><span class="na">id</span><span class="o">.</span><span class="na">textView</span><span class="o">);</span>
<span class="kd">final</span> <span class="kt">int</span> <span class="n">foregroundTextColor</span> <span class="o">=</span> <span class="n">ContextCompat</span><span class="o">.</span><span class="na">getColor</span><span class="o">(</span><span class="k">this</span><span class="o">,</span> <span class="n">R</span><span class="o">.</span><span class="na">color</span><span class="o">.</span><span class="na">T1</span><span class="o">);</span>
<span class="kd">final</span> <span class="kt">int</span> <span class="n">linkTextColor</span> <span class="o">=</span> <span class="n">ContextCompat</span><span class="o">.</span><span class="na">getColor</span><span class="o">(</span><span class="k">this</span><span class="o">,</span> <span class="n">R</span><span class="o">.</span><span class="na">color</span><span class="o">.</span><span class="na">colorPrimary</span><span class="o">);</span>
<span class="kd">final</span> <span class="kt">int</span> <span class="n">normalTextColor</span> <span class="o">=</span> <span class="n">ContextCompat</span><span class="o">.</span><span class="na">getColor</span><span class="o">(</span><span class="k">this</span><span class="o">,</span> <span class="n">R</span><span class="o">.</span><span class="na">color</span><span class="o">.</span><span class="na">R1</span><span class="o">);</span>
<span class="kd">final</span> <span class="kt">int</span> <span class="n">pressedTextColor</span> <span class="o">=</span> <span class="n">ContextCompat</span><span class="o">.</span><span class="na">getColor</span><span class="o">(</span><span class="k">this</span><span class="o">,</span> <span class="n">R</span><span class="o">.</span><span class="na">color</span><span class="o">.</span><span class="na">W1</span><span class="o">);</span>
<span class="kd">final</span> <span class="kt">int</span> <span class="n">pressedBackgroundColor</span> <span class="o">=</span> <span class="n">ContextCompat</span><span class="o">.</span><span class="na">getColor</span><span class="o">(</span><span class="k">this</span><span class="o">,</span> <span class="n">R</span><span class="o">.</span><span class="na">color</span><span class="o">.</span><span class="na">B1</span><span class="o">);</span>
<span class="kd">final</span> <span class="n">Typeface</span> <span class="n">georgiaTypeface</span> <span class="o">=</span> <span class="n">Typeface</span><span class="o">.</span><span class="na">createFromAsset</span><span class="o">(</span><span class="n">getAssets</span><span class="o">(),</span> <span class="s">"fonts/Georgia Italic.ttf"</span><span class="o">);</span>
<span class="n">RichText</span> <span class="n">richText</span> <span class="o">=</span> <span class="k">new</span> <span class="n">RichText</span><span class="o">.</span><span class="na">Builder</span><span class="o">()</span>
<span class="c1">//'c'标签添加ClickSpan</span>
<span class="o">.</span><span class="na">addBlockTypeSpan</span><span class="o">(</span><span class="k">new</span> <span class="n">ClickSpan</span><span class="o">(</span>
<span class="n">normalTextColor</span><span class="o">,</span>
<span class="n">pressedTextColor</span><span class="o">,</span>
<span class="n">pressedBackgroundColor</span><span class="o">,</span>
<span class="k">new</span> <span class="n">ClickSpan</span><span class="o">.</span><span class="na">OnClickListener</span><span class="o">()</span> <span class="o">{</span>
<span class="nd">@Override</span>
<span class="kd">public</span> <span class="kt">void</span> <span class="nf">onClick</span><span class="o">(</span><span class="n">CharSequence</span> <span class="n">text</span><span class="o">,</span> <span class="kt">float</span> <span class="n">rawX</span><span class="o">,</span> <span class="kt">float</span> <span class="n">rawY</span><span class="o">)</span> <span class="o">{</span>
<span class="n">Toast</span><span class="o">.</span><span class="na">makeText</span><span class="o">(</span><span class="n">MainActivity</span><span class="o">.</span><span class="na">this</span><span class="o">,</span> <span class="n">text</span><span class="o">,</span> <span class="n">Toast</span><span class="o">.</span><span class="na">LENGTH_SHORT</span><span class="o">).</span><span class="na">show</span><span class="o">();</span>
<span class="o">}</span>
<span class="o">}),</span> <span class="s">"c"</span><span class="o">)</span>
<span class="c1">//'f','t'标签添加ForegroundColorSpan</span>
<span class="o">.</span><span class="na">addBlockTypeSpan</span><span class="o">(</span><span class="k">new</span> <span class="n">IStyleSpan</span><span class="o">()</span> <span class="o">{</span>
<span class="nd">@Override</span>
<span class="kd">public</span> <span class="n">CharacterStyle</span> <span class="nf">getStyleSpan</span><span class="o">()</span> <span class="o">{</span>
<span class="k">return</span> <span class="k">new</span> <span class="n">ForegroundColorSpan</span><span class="o">(</span><span class="n">foregroundTextColor</span><span class="o">);</span>
<span class="o">}</span>
<span class="o">},</span> <span class="s">"f"</span><span class="o">,</span> <span class="s">"t"</span><span class="o">)</span>
<span class="c1">//'bi'标签添加StyleSpan</span>
<span class="o">.</span><span class="na">addBlockTypeSpan</span><span class="o">(</span><span class="k">new</span> <span class="n">IStyleSpan</span><span class="o">()</span> <span class="o">{</span>
<span class="nd">@Override</span>
<span class="kd">public</span> <span class="n">CharacterStyle</span> <span class="nf">getStyleSpan</span><span class="o">()</span> <span class="o">{</span>
<span class="k">return</span> <span class="k">new</span> <span class="n">StyleSpan</span><span class="o">(</span><span class="n">Typeface</span><span class="o">.</span><span class="na">BOLD_ITALIC</span><span class="o">);</span>
<span class="o">}</span>
<span class="o">},</span> <span class="s">"bi"</span><span class="o">)</span>
<span class="c1">//'s'标签添加TextAppearanceSpan</span>
<span class="o">.</span><span class="na">addBlockTypeSpan</span><span class="o">(</span><span class="k">new</span> <span class="n">IStyleSpan</span><span class="o">()</span> <span class="o">{</span>
<span class="nd">@Override</span>
<span class="kd">public</span> <span class="n">CharacterStyle</span> <span class="nf">getStyleSpan</span><span class="o">()</span> <span class="o">{</span>
<span class="k">return</span> <span class="k">new</span> <span class="n">TextAppearanceSpan</span><span class="o">(</span><span class="n">MainActivity</span><span class="o">.</span><span class="na">this</span><span class="o">,</span> <span class="n">R</span><span class="o">.</span><span class="na">style</span><span class="o">.</span><span class="na">TextSize</span><span class="o">);</span>
<span class="o">}</span>
<span class="o">},</span> <span class="s">"s"</span><span class="o">)</span>
<span class="c1">//'t'标签添加FontTypefaceSpan</span>
<span class="o">.</span><span class="na">addBlockTypeSpan</span><span class="o">(</span><span class="k">new</span> <span class="n">FontTypefaceSpan</span><span class="o">(</span><span class="n">georgiaTypeface</span><span class="o">),</span> <span class="s">"t"</span><span class="o">)</span>
<span class="c1">//超链接标签默认'a'</span>
<span class="o">.</span><span class="na">addLinkTypeSpan</span><span class="o">(</span><span class="k">new</span> <span class="n">LinkClickSpan</span><span class="o">(</span>
<span class="n">linkTextColor</span><span class="o">,</span>
<span class="n">pressedTextColor</span><span class="o">,</span>
<span class="n">pressedBackgroundColor</span><span class="o">,</span>
<span class="k">new</span> <span class="n">LinkClickSpan</span><span class="o">.</span><span class="na">OnLinkClickListener</span><span class="o">()</span> <span class="o">{</span>
<span class="nd">@Override</span>
<span class="kd">public</span> <span class="kt">void</span> <span class="nf">onClick</span><span class="o">(</span><span class="n">String</span> <span class="n">url</span><span class="o">)</span> <span class="o">{</span>
<span class="n">Toast</span><span class="o">.</span><span class="na">makeText</span><span class="o">(</span><span class="n">MainActivity</span><span class="o">.</span><span class="na">this</span><span class="o">,</span> <span class="n">url</span><span class="o">,</span> <span class="n">Toast</span><span class="o">.</span><span class="na">LENGTH_SHORT</span><span class="o">).</span><span class="na">show</span><span class="o">();</span>
<span class="o">}</span>
<span class="o">})</span>
<span class="o">)</span>
<span class="o">.</span><span class="na">build</span><span class="o">();</span>
<span class="c1">//notice: if set click span, you must invoke this method.</span>
<span class="n">richText</span><span class="o">.</span><span class="na">with</span><span class="o">(</span><span class="n">textView</span><span class="o">);</span>
<span class="c1">//不同标签,指定不同span。</span>
<span class="n">String</span> <span class="n">tagString</span> <span class="o">=</span> <span class="s">"The <a href='https://en.wikipedia.org/wiki/Rich_Text_Format'>Rich Text Format</a> "</span> <span class="o">+</span>
<span class="s">"is a <c>proprietary</c> <f>document</f> file format with published <bi>specification</bi> "</span> <span class="o">+</span>
<span class="s">"developed by <t>Microsoft Corporation</t> from 1987 until 2008 for <s>cross-platform</s> document interchange with Microsoft products."</span><span class="o">;</span>
<span class="n">textView</span><span class="o">.</span><span class="na">setText</span><span class="o">(</span><span class="n">richText</span><span class="o">.</span><span class="na">parse</span><span class="o">(</span><span class="n">tagString</span><span class="o">));</span>
</pre></div>
<p>有疑问或者觉得不对的地方还请指正,谢谢。</p>
<p>Rich Text源码:<a href="https://github.com/xymelon/richtext" target="_blank">https://github.com/xymelon/richtext</a></p>Android仿腾讯新闻创意截屏2015-10-27T19:34:00+08:002015-10-27T19:34:00+08:00xymelontag:www.xycoding.com,2015-10-27:/articles/2015/10/27/android-novcapture/<p>前不久使用腾讯新闻的时候,偶然间发现一个很酷炫的功能——创意截屏,大概就是截取当前屏幕生成一张图片,然后可以在这张图片上添加多个自定义标签。功能支持多种手势操作,包括单手拖动、缩放、旋转、删除;双手缩放、旋转等,并可动态更改文字标签内容,使用过程中基本没有卡顿现象发生。本文主要就讲讲我是怎么实现创意截屏功能,当然条条大路通罗马,每个人可能都有自己的实现方式,欢迎大家拍砖并提出建议。</p>
<p>前不久使用腾讯新闻的时候,偶然间发现一个很酷炫的功能——创意截屏,大概就是截取当前屏幕生成一张图片,然后可以在这张图片上添加多个自定义标签。功能支持多种手势操作,包括单手拖动、缩放、旋转、删除;双手缩放、旋转等,并可动态更改文字标签内容,使用过程中基本没有卡顿现象发生。本文主要就讲讲我是怎么实现创意截屏功能,当然条条大路通罗马,每个人可能都有自己的实现方式,欢迎大家拍砖并提出建议。</p>
<h3>截屏预览</h3>
<p>首先看看实现后的整个创意截屏的样子吧,最直观。PS:在我的几个测试机上基本没有卡顿现象,很流畅。</p>
<p><img alt="image" src="/images/novcapture.gif"></p>
<p>(图1)打开APP后,截取屏幕进入添加标签即可体验。</p>
<h3>关键细节</h3>
<p>起初看到创意截屏这个功能,私以为比较简单,结果在进行编码过程中,才发现里面有很多细节需要进行考虑。</p>
<ol>
<li>
<p>标签(图中虚线框容器)不能完全使用ViewGroup进行拼装,不然进行缩放、旋转、拖动时会出现两个问题:</p>
<p><strong>性能问题,会出现明显的卡顿现象;</strong></p>
<p><strong>显示问题,当标签进行缩放时,其实标签左上删除、右下缩放与旋转按钮一直保持大小不变。若使用整体缩放,则会出现一起放大缩小,不符合效果;当然也可进行特殊处理,比如针对这俩按钮进行反比例缩放。</strong></p>
<p>在本文中,<strong>关键解决方案在于将整个标签内容ViewGroup生成Bitmap,然后维护标签、删除、拖放按钮等的绘制矩阵,在onDraw中自定义绘画</strong>,非常灵活,性能很不错。</p>
</li>
<li>
<p>缩放、旋转、拖动相关,其中每个功能都有多种实现方式,比如旋转可用库函数setRotation或canvas.rotate画布操作、拖动可动态改变margin或使用canvas.translate实现等。先不说使用库函数会遇到兼容低版本问题,而且对性能也有很大影响,如当缩放比例很大时,拖动过程中会出现非常明显的卡顿现象。在本文中,<strong>针对标签的使用场景(频繁缩放、旋转、拖动等)选用画布Canvas操作才是上策</strong>。</p>
</li>
<li>
<p>事件分发,当容器中存在多个标签时,就会判断是哪个标签接收并处理事件。通过上段知道,我放弃使用库函数进行缩放、旋转、拖动,选用画布Canvas进行操作,同样,Canvas操作也带来了另外的问题,如:</p>
<ul>
<li>事件接收的坐标点不会跟着Canvas改变,如View在A坐标canvas.translate移动到B坐标后,View接收事件的坐标点还是会停留在原A坐标。因此,我们还需<strong>手动维护虚线框、删除、缩放与旋转按钮的当前绘制矩阵,以便事件分发时判定</strong>。</li>
<li>
<p>当View旋转后,如何判定坐标是否在View区域内。如下面图2所示,假设蓝色矩形为我们维护的原始矩阵matrix,当我们调用matrix.postRotate进行旋转操作后,matrix会变成图2中的虚线框区域,而实际上应该为绿色区域,若不进行处理,则会判断错误。<strong>本文中使用了小技巧,即我们维护一个未旋转的matrix(包含缩放、拖动)和一个旋转角度变量rotation。当判断图中点A是否在绿色区域内,则可将其反向旋转rotation后变成点B,判断点B是否在蓝色矩形中即可</strong>。</p>
<p><img alt="image" src="/images/novcapture_1.png"></p>
<p>(图2)坐标点判断示例。</p>
</li>
</ul>
</li>
<li>
<p>虚线框绘画,使用<a href="http://developer.android.com/reference/android/graphics/DashPathEffect.html" target="_blank">DashPathEffect</a>绘制虚线框时,为了能让其动起来,不可避免地会根据phase创建很多对象,若不能有效处理,则会导致频繁地内存回收,影响性能。本文中优化方式是根据虚线框实、虚线长度创建多个对象循环使用,如:</p>
<div class="highlight"><pre><span></span>private PathEffect getPathEffect() {
// phase值在 0 - mDashMinSize 之间循环
mDashPhase %= mDashMinSize;
PathEffect effect = mPathEffects[mDashPhase];
// 判定当前phase的DashPathEffect对象是否创建,是则循环使用,反之新建
if (effect == null) {
effect = new DashPathEffect(mDashFloats, mDashPhase);
mPathEffects[mDashPhase] = effect;
}
mDashPhase++;
return effect;
}
</pre></div>
</li>
<li>
<p>软键盘遮挡输入框或界面上移。当我们需要编辑文本的时候,基本都会遇到软键盘把输入框遮挡或导致界面整个上提的问题,google一圈下来基本都是说调整android:windowSoftInputMode属性为"adjustResize"或"adjustPan",而这并不能完全解决问题还需要调整布局,比较麻烦。本文使用的方法是将输入框放入dialog或popupwindow中,这不但可以解决前述问题,还能将代码组织得更模块化。</p>
</li>
</ol>
<h3>代码分析</h3>
<p><img alt="image" src="/images/novcapture_2.png"></p>
<p>(图2)代码组织结构。</p>
<p>先说说整个代码结构吧,核心在图2 labeller 库中的<code>CaptureView</code>和<code>DashFrameLayout</code>。</p>
<h4>CaptureView</h4>
<p>本类主要负责整个创意截屏界面处理,包含内容、下方添加标签、输入框等区域。
有个关键地方就是当设置完成图片内容后,内容区域的高宽度需要完全匹配图片大小,否则当进行拖动操作时,标签会移出图片,最后的保存操作时也会出现空白区域,非常不美观。关键代码如下:</p>
<div class="highlight"><pre><span></span> private void setImageBitmap(Bitmap bitmap) {
mContainerLayout.getLayoutParams().width = bitmap.getWidth();
mContainerLayout.getLayoutParams().height = bitmap.getHeight();
mImageIV.getLayoutParams().width = bitmap.getWidth();
mImageIV.getLayoutParams().height = bitmap.getHeight();
mImageIV.setImageBitmap(bitmap);
}
</pre></div>
<p>添加标签时,还需维护当前处于焦点的标签,以便处理是否显示虚线框,如下:</p>
<div class="highlight"><pre><span></span> private void addLabel(View view) {
final DashFrameLayout dashFrameLayout = new DashFrameLayout(getContext());
// 添加监听器,记录状态
dashFrameLayout.setLabelListener(new DashFrameLayout.LabelListener() {
@Override
public void onFocus() {
mFocusLabel = dashFrameLayout;
}
@Override
public void onClick() {
onLabelClick();
}
});
dashFrameLayout.addView(view);
hideFocusLabelDashBorder();
mFocusLabel = dashFrameLayout;
mContainerLayout.addView(dashFrameLayout);
}
</pre></div>
<h4>DashFrameLayout</h4>
<p>本类应该是创意截屏功能的核心所在,即标签(虚线框容器),包含几大部分:左上角删除按钮、右下角拖放与旋转按钮、中间虚线框容器、标签内容。</p>
<p>初始时,需计算各个部分的绘制矩阵,并将标签内容生成bitmap,提高性能。核心代码如下:</p>
<div class="highlight"><pre><span></span> Rect rect = new Rect();
mChildView.getLocalVisibleRect(rect);
// 计算虚线框绘制位置,注意虚线框与label之间存在margin
mDashRectF.left = rect.left - margin;
mDashRectF.top = rect.top - margin;
mDashRectF.right = rect.right + margin;
mDashRectF.bottom = rect.bottom + margin;
// 计算删除按钮初始中心坐标
mDelPoint[0] = mDashRectF.left;
mDelPoint[1] = mDashRectF.top;
// 计算拖放按钮初始中心坐标
mDragPoint[0] = mDashRectF.right;
mDragPoint[1] = mDashRectF.bottom;
// 将view转换为bitmap,便于旋转、缩放时提高性能
mLabelBitmap = ImageUtils.captureScreenByDraw(mChildView);
</pre></div>
<p>然后根据维护的矩阵,自定义onDraw方法,如下:</p>
<div class="highlight"><pre><span></span> @Override
protected void onDraw(Canvas canvas) {
// 画标签
canvas.drawBitmap(mLabelBitmap, mViewMatrix, null);
if (mShowDashBorder) {
// 画虚线框
mDashPaint.setPathEffect(getPathEffect());
canvas.save();
canvas.concat(mViewMatrix);
canvas.drawPath(mDashPath, mDashPaint);
canvas.restore();
// 画删除按钮:左上角
canvas.drawBitmap(mDelBitmap, null, mDelDrawRectF, null);
// 画拖放按钮:右下角
canvas.drawBitmap(mDragBitmap, null, mDragDrawRectF, null);
}
}
</pre></div>
<p>其他很关键的比如事件分发,因代码过长,不在此累述,请参考源代码中onTouchEvent实现方法。PS:单手旋转、缩放操作,可将其认为双手操作,只不过其中一只手固定在标签中心。</p>
<p>有疑问或者觉得不对的地方还请指正,谢谢。</p>
<p>创意截屏源码:<a href="https://github.com/xymelon/nov-capture" target="_blank">https://github.com/xymelon/nov-capture</a></p>Android仿微信上传头像(缩放、拖动、裁剪)2014-11-21T17:18:00+08:002014-11-21T17:18:00+08:00xymelontag:www.xycoding.com,2014-11-21:/articles/2014/11/21/android-clipimage/<p>相信大多数做过Android APP的同学们都会上传头像,而用户选择的图片可能并不符合我们APP的要求且不美观,所以对于图片尺寸的控制(缩放、拖动、裁剪等)就非常重要了。本文就讲讲我在项目中使用的仿微信上传头像功能,欢迎大家指正。</p>
<p>相信大多数做过Android APP的同学们都会上传头像,而用户选择的图片可能并不符合我们APP的要求且不美观,所以对于图片尺寸的控制(缩放、拖动、裁剪等)就非常重要了。本文就讲讲我在项目中使用的仿微信上传头像功能,欢迎大家指正。</p>
<h3>裁剪图片原理</h3>
<p>关于裁剪指定位置的图片原理非常简单,本文中采用的是对屏幕进行截取屏幕,然后再裁剪指定坐标、宽度位置的图片。了解裁剪图片原理后,即可进行编码工作。</p>
<h3>自定义裁剪框</h3>
<p>如微信上传头像界面,裁剪框的作用是在屏幕上呈现一个白色的矩形边框,边框以外的界面都是半透明的形式。当图片在屏幕中拖动时,可以非常明确地提醒矩形框中的图片才是我们所需要的。以下的代码即自定义裁剪框,大家可看代码中注释,非常简单(即在屏幕中画出一个白色的矩形线框,边框以外的上下左右画出半透明背景)。</p>
<p>在本代码中,自定义了一个<code>OnDrawListenerComplete</code>接口,里面定义了<code>onDrawCompelete</code>方法。它的作用是什么?不知大家仔细观察过微信上传头像功能没,当我们选择图片后,不管图片多大多小,多宽多窄,它总能保证图片的宽度满足裁剪框或者高度满足裁剪框,即按裁剪框的比例缩放图片,这样用户在上传时就非常方便呢。该接口的作用就是当裁剪框绘画完成后即可调用回调函数,进行图片处理,以满足裁剪框的大小。</p>
<div class="highlight"><pre><span></span>public class ClipView extends View {
private Paint paint = new Paint();
private Paint borderPaint = new Paint();
/**
* 自定义顶部栏高度,如不是自定义,则默认为0即可
*/
private int customTopBarHeight = 0;
/**
* 裁剪框长宽比,默认4:3
*/
private double clipRatio = 0.75;
/**
* 裁剪框宽度
*/
private int clipWidth = -1;
/**
* 裁剪框高度
*/
private int clipHeight = -1;
/**
* 裁剪框左边空留宽度
*/
private int clipLeftMargin = 0;
/**
* 裁剪框上边空留宽度
*/
private int clipTopMargin = 0;
/**
* 裁剪框边框宽度
*/
private int clipBorderWidth = 1;
private boolean isSetMargin = false;
private OnDrawListenerComplete listenerComplete;
public ClipView(Context context) {
this(context, null);
}
public ClipView(Context context, AttributeSet attrs) {
this(context, attrs, 0);
}
public ClipView(Context context, AttributeSet attrs, int defStyle) {
super(context, attrs, defStyle);
paint.setAlpha(200);
borderPaint.setStyle(Style.STROKE);
borderPaint.setColor(Color.WHITE);
borderPaint.setStrokeWidth(clipBorderWidth);
}
@Override
protected void onDraw(Canvas canvas) {
super.onDraw(canvas);
int width = this.getWidth();
int height = this.getHeight();
// 如没有显式设置裁剪框高度和宽度,取默认值
if (clipWidth == -1 || clipHeight == -1) {
clipWidth = width - 50;
clipHeight = (int) (clipWidth * clipRatio);
// 横屏
if (width > height) {
clipHeight = height - customTopBarHeight - 50;
clipWidth = (int) (clipHeight / clipRatio);
}
}
// 如没有显示设置裁剪框左和上预留宽度,取默认值
if (!isSetMargin) {
clipLeftMargin = (width - clipWidth) / 2;
clipTopMargin = (height - clipHeight) / 2;
}
// 防止横屏时,覆盖标题栏
if (clipTopMargin <= customTopBarHeight) {
clipTopMargin = customTopBarHeight + 20;
}
// 画阴影
// top
canvas.drawRect(0, customTopBarHeight, width, clipTopMargin, paint);
// left
canvas.drawRect(0, clipTopMargin, clipLeftMargin, clipTopMargin
+ clipHeight, paint);
// right
canvas.drawRect(clipLeftMargin + clipWidth, clipTopMargin, width,
clipTopMargin + clipHeight, paint);
// bottom
canvas.drawRect(0, clipTopMargin + clipHeight, width, height, paint);
// 画边框
canvas.drawRect(clipLeftMargin, clipTopMargin, clipLeftMargin
+ clipWidth, clipTopMargin + clipHeight, borderPaint);
if (listenerComplete != null) {
listenerComplete.onDrawCompelete();
}
}
public int getCustomTopBarHeight() {
return customTopBarHeight;
}
public void setCustomTopBarHeight(int customTopBarHeight) {
this.customTopBarHeight = customTopBarHeight;
}
public double getClipRatio() {
return clipRatio;
}
public void setClipRatio(double clipRatio) {
this.clipRatio = clipRatio;
}
public int getClipWidth() {
// 减clipBorderWidth原因:截图时去除边框白线
return clipWidth - clipBorderWidth;
}
public void setClipWidth(int clipWidth) {
this.clipWidth = clipWidth;
}
public int getClipHeight() {
return clipHeight - clipBorderWidth;
}
public void setClipHeight(int clipHeight) {
this.clipHeight = clipHeight;
}
public int getClipLeftMargin() {
return clipLeftMargin + clipBorderWidth;
}
public void setClipLeftMargin(int clipLeftMargin) {
this.clipLeftMargin = clipLeftMargin;
isSetMargin = true;
}
public int getClipTopMargin() {
return clipTopMargin + clipBorderWidth;
}
public void setClipTopMargin(int clipTopMargin) {
this.clipTopMargin = clipTopMargin;
isSetMargin = true;
}
public void addOnDrawCompleteListener(OnDrawListenerComplete listener) {
this.listenerComplete = listener;
}
public void removeOnDrawCompleteListener() {
this.listenerComplete = null;
}
/**
* 裁剪区域画完时调用接口
*
* @author Cow
*/
public interface OnDrawListenerComplete {
public void onDrawCompelete();
}
}
</pre></div>
<h3>使用裁剪框</h3>
<p>使用裁剪框就非常简单了,将该裁剪框以<code>this.addContentView</code>的方式添加到要使用的Activity中。本代码中,即<code>initClipView</code>方法,至于为什么要在<code>observer.addOnGlobalLayoutListener</code>中初始化裁剪框,是因为本例的顶部栏是自定义的,需等该头部的高度确定以后才能对裁剪框进行初始化,如果你没有使用自定义顶部栏,那么就无所谓了。大家可以看到在<code>observer.addOnGlobalLayoutListener</code>回调接口中,有一大段计算缩放、平移等的语句,这些代码的作用即是上文所讨论的,让用户无论选择多大的图片,都让它满足裁剪框的高宽度。然后再加上针对<code>ImageView</code>容器的拖动、缩放等效果即可。</p>
<div class="highlight"><pre><span></span>public class ClipPictureActivity extends Activity implements OnTouchListener,
OnClickListener {
private ImageView srcPic;
private View sure;
private ClipView clipview;
private Matrix matrix = new Matrix();
private Matrix savedMatrix = new Matrix();
/** 动作标志:无 */
private static final int NONE = 0;
/** 动作标志:拖动 */
private static final int DRAG = 1;
/** 动作标志:缩放 */
private static final int ZOOM = 2;
/** 初始化动作标志 */
private int mode = NONE;
/** 记录起始坐标 */
private PointF start = new PointF();
/** 记录缩放时两指中间点坐标 */
private PointF mid = new PointF();
private float oldDist = 1f;
private Bitmap bitmap;
@Override
public void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.main);
srcPic = (ImageView) this.findViewById(R.id.src_pic);
srcPic.setOnTouchListener(this);
ViewTreeObserver observer = srcPic.getViewTreeObserver();
observer.addOnGlobalLayoutListener(new OnGlobalLayoutListener() {
@SuppressWarnings("deprecation")
public void onGlobalLayout() {
srcPic.getViewTreeObserver().removeGlobalOnLayoutListener(this);
initClipView(srcPic.getTop());
}
});
sure = (View) this.findViewById(R.id.sure);
sure.setOnClickListener(this);
}
/**
* 初始化截图区域,并将源图按裁剪框比例缩放
*
* @param top
*/
private void initClipView(int top) {
bitmap = BitmapFactory.decodeResource(this.getResources(),
R.drawable.pic);
clipview = new ClipView(ClipPictureActivity.this);
clipview.setCustomTopBarHeight(top);
clipview.addOnDrawCompleteListener(new OnDrawListenerComplete() {
public void onDrawCompelete() {
clipview.removeOnDrawCompleteListener();
int clipHeight = clipview.getClipHeight();
int clipWidth = clipview.getClipWidth();
int midX = clipview.getClipLeftMargin() + (clipWidth / 2);
int midY = clipview.getClipTopMargin() + (clipHeight / 2);
int imageWidth = bitmap.getWidth();
int imageHeight = bitmap.getHeight();
// 按裁剪框求缩放比例
float scale = (clipWidth * 1.0f) / imageWidth;
if (imageWidth > imageHeight) {
scale = (clipHeight * 1.0f) / imageHeight;
}
// 起始中心点
float imageMidX = imageWidth * scale / 2;
float imageMidY = clipview.getCustomTopBarHeight()
+ imageHeight * scale / 2;
srcPic.setScaleType(ScaleType.MATRIX);
// 缩放
matrix.postScale(scale, scale);
// 平移
matrix.postTranslate(midX - imageMidX, midY - imageMidY);
srcPic.setImageMatrix(matrix);
srcPic.setImageBitmap(bitmap);
}
});
this.addContentView(clipview, new LayoutParams(
LayoutParams.MATCH_PARENT, LayoutParams.MATCH_PARENT));
}
public boolean onTouch(View v, MotionEvent event) {
ImageView view = (ImageView) v;
switch (event.getAction() & MotionEvent.ACTION_MASK) {
case MotionEvent.ACTION_DOWN:
savedMatrix.set(matrix);
// 设置开始点位置
start.set(event.getX(), event.getY());
mode = DRAG;
break;
case MotionEvent.ACTION_POINTER_DOWN:
oldDist = spacing(event);
if (oldDist > 10f) {
savedMatrix.set(matrix);
midPoint(mid, event);
mode = ZOOM;
}
break;
case MotionEvent.ACTION_UP:
case MotionEvent.ACTION_POINTER_UP:
mode = NONE;
break;
case MotionEvent.ACTION_MOVE:
if (mode == DRAG) {
matrix.set(savedMatrix);
matrix.postTranslate(event.getX() - start.x, event.getY()
- start.y);
} else if (mode == ZOOM) {
float newDist = spacing(event);
if (newDist > 10f) {
matrix.set(savedMatrix);
float scale = newDist / oldDist;
matrix.postScale(scale, scale, mid.x, mid.y);
}
}
break;
}
view.setImageMatrix(matrix);
return true;
}
/**
* 多点触控时,计算最先放下的两指距离
*
* @param event
* @return
*/
private float spacing(MotionEvent event) {
float x = event.getX(0) - event.getX(1);
float y = event.getY(0) - event.getY(1);
return (float) Math.sqrt(x * x + y * y);
}
/**
* 多点触控时,计算最先放下的两指中心坐标
*
* @param point
* @param event
*/
private void midPoint(PointF point, MotionEvent event) {
float x = event.getX(0) + event.getX(1);
float y = event.getY(0) + event.getY(1);
point.set(x / 2, y / 2);
}
public void onClick(View v) {
Bitmap clipBitmap = getBitmap();
ByteArrayOutputStream baos = new ByteArrayOutputStream();
clipBitmap.compress(Bitmap.CompressFormat.JPEG, 100, baos);
byte[] bitmapByte = baos.toByteArray();
Intent intent = new Intent();
intent.setClass(getApplicationContext(), PreviewActivity.class);
intent.putExtra("bitmap", bitmapByte);
startActivity(intent);
}
/**
* 获取裁剪框内截图
*
* @return
*/
private Bitmap getBitmap() {
// 获取截屏
View view = this.getWindow().getDecorView();
view.setDrawingCacheEnabled(true);
view.buildDrawingCache();
// 获取状态栏高度
Rect frame = new Rect();
this.getWindow().getDecorView().getWindowVisibleDisplayFrame(frame);
int statusBarHeight = frame.top;
Bitmap finalBitmap = Bitmap.createBitmap(view.getDrawingCache(),
clipview.getClipLeftMargin(), clipview.getClipTopMargin()
+ statusBarHeight, clipview.getClipWidth(),
clipview.getClipHeight());
// 释放资源
view.destroyDrawingCache();
return finalBitmap;
}
}
</pre></div>
<p>最后,效果图如下:</p>
<p><img alt="dowmload" src="/images/clip_1_1.png"></p>
<p>(图1.1)当用户选择图片后,初始化界面,大家可以看到,图片的高度符合裁剪框的高度,相应的图片宽度也按比例缩放。</p>
<p><img alt="dowmload" src="/images/clip_1_2.png"></p>
<p>(图1.2)针对初始界面进行裁剪截图操作。</p>
<p><img alt="dowmload" src="/images/clip_2_1.png"></p>
<p>(图2.1)往上拖动图片,大家可以看到白云飘出裁剪框了。</p>
<p><img alt="dowmload" src="/images/clip_2_2.png"></p>
<p>(图2.2)针对拖动图片进行裁剪截图操作,可以看到截图中没有白云了。</p>
<p><img alt="dowmload" src="/images/clip_3_1.png"></p>
<p>(图3.1)缩放图片,大家可以看到图片被放大了,只有一半白云,下方的湖面也少了。</p>
<p><img alt="dowmload" src="/images/clip_3_2.png"></p>
<p>(图3.2)针对缩放图片进行裁剪截图操作,同样可以发现操作成功了。</p>
<h3>注意</h3>
<p>本例中,Activity之间传递截图是以Bitmap形式传递的,这是大忌,这有可能因Bitmap过大而导致APP崩溃。本文完全是出于方便,读者在实际使用中需注意。</p>
<p>有疑问或者觉得不对的地方还请指正,谢谢。</p>
<p><a href="https://github.com/cowfighting/clippicture" target="_blank">GitHub源码下载:DEMO</a></p>Android异步批量下载图片并缓存2014-07-29T19:55:00+08:002014-07-29T19:55:00+08:00xymelontag:www.xycoding.com,2014-07-29:/articles/2014/07/29/android-async-images-download/<h3>前言</h3>
<p>接触android开发不久,近段时间需实现一个批量下载图片并显示的小功能。在网上搜索了一圈,发现国内外网上异步加载的例子太多太杂,要么是加载大图decode时报OOM异常,要么内存急剧上升不稳定。所以在前辈们的基础上,做了一些优化,特共享出来,欢迎大家指正。这里主要参见了以下两篇文章,非常感谢:</p>
<p><a href="http://blog.csdn.net/guolin_blog/article/details/9526203" target="_blank">Android照片墙应用实现,再多的图片也不怕崩溃</a></p>
<p><a href="http://blog.csdn.net/xiaanming/article/details/9825113" target="_blank">Android 异步加载图片,使用LruCache和SD卡或手机缓存,效果非常的流畅</a></p>
<p>在巨人的肩膀上,主要优化了以下几个地方:</p>
<ol>
<li>下载大图decode时,可根据View大小自动缩放图片,不在出现<code>OOM</code>和<code>SkImageDecoder::Factory returned null</code>错误</li>
<li>图片下载失败时,可自定义失败重试次数</li>
<li>记录正在下载的任务,防止屏幕滚动时多次下载</li>
<li>缓存目录容量大于给定限制时,清空文件缓存</li>
<li>其他一些小问题</li>
</ol>
<h3>前言</h3>
<p>接触android开发不久,近段时间需实现一个批量下载图片并显示的小功能。在网上搜索了一圈,发现国内外网上异步加载的例子太多太杂,要么是加载大图decode时报OOM异常,要么内存急剧上升不稳定。所以在前辈们的基础上,做了一些优化,特共享出来,欢迎大家指正。这里主要参见了以下两篇文章,非常感谢:</p>
<p><a href="http://blog.csdn.net/guolin_blog/article/details/9526203" target="_blank">Android照片墙应用实现,再多的图片也不怕崩溃</a></p>
<p><a href="http://blog.csdn.net/xiaanming/article/details/9825113" target="_blank">Android 异步加载图片,使用LruCache和SD卡或手机缓存,效果非常的流畅</a></p>
<p>在巨人的肩膀上,主要优化了以下几个地方:</p>
<ol>
<li>下载大图decode时,可根据View大小自动缩放图片,不在出现<code>OOM</code>和<code>SkImageDecoder::Factory returned null</code>错误</li>
<li>图片下载失败时,可自定义失败重试次数</li>
<li>记录正在下载的任务,防止屏幕滚动时多次下载</li>
<li>缓存目录容量大于给定限制时,清空文件缓存</li>
<li>其他一些小问题</li>
</ol>
<h3>工具类Utils</h3>
<p>Utils类主要封装了一些文件操作的基本方法,包括创建、删除、获取文件大小等。</p>
<div class="highlight"><pre><span></span><span class="kr">public</span> <span class="kr">class</span> <span class="nx">Utils</span> <span class="p">{</span>
<span class="kr">private</span> <span class="kr">static</span> <span class="kr">final</span> <span class="nb">String</span> <span class="nx">Util_LOG</span> <span class="o">=</span> <span class="nx">makeLogTag</span><span class="p">(</span><span class="nx">Utils</span><span class="p">.</span><span class="kr">class</span><span class="p">);</span>
<span class="kr">public</span> <span class="kr">static</span> <span class="nb">String</span> <span class="nx">makeLogTag</span><span class="p">(</span><span class="nx">Class</span><span class="cp"><?</span><span class="o">></span> <span class="nx">cls</span><span class="p">)</span> <span class="p">{</span>
<span class="k">return</span> <span class="nx">cls</span><span class="o">.</span><span class="nx">getName</span><span class="p">();</span>
<span class="p">}</span>
<span class="k">public</span> <span class="k">static</span> <span class="nx">void</span> <span class="nx">showToast</span><span class="p">(</span><span class="nx">Context</span> <span class="nx">context</span><span class="p">,</span> <span class="nx">String</span> <span class="nx">str</span><span class="p">)</span> <span class="p">{</span>
<span class="nx">Toast</span><span class="o">.</span><span class="nx">makeText</span><span class="p">(</span><span class="nx">context</span><span class="p">,</span> <span class="nx">str</span><span class="p">,</span> <span class="nx">Toast</span><span class="o">.</span><span class="nx">LENGTH_SHORT</span><span class="p">)</span><span class="o">.</span><span class="nx">show</span><span class="p">();</span>
<span class="p">}</span>
<span class="sd">/**</span>
<span class="sd"> * 检查是否存在SD卡</span>
<span class="sd"> * </span>
<span class="sd"> * @return</span>
<span class="sd"> */</span>
<span class="k">public</span> <span class="k">static</span> <span class="nx">boolean</span> <span class="nx">hasSdcard</span><span class="p">()</span> <span class="p">{</span>
<span class="nx">String</span> <span class="nx">state</span> <span class="o">=</span> <span class="nx">Environment</span><span class="o">.</span><span class="nx">getExternalStorageState</span><span class="p">();</span>
<span class="k">if</span> <span class="p">(</span><span class="nx">state</span><span class="o">.</span><span class="nx">equals</span><span class="p">(</span><span class="nx">Environment</span><span class="o">.</span><span class="nx">MEDIA_MOUNTED</span><span class="p">))</span> <span class="p">{</span>
<span class="k">return</span> <span class="k">true</span><span class="p">;</span>
<span class="p">}</span> <span class="k">else</span> <span class="p">{</span>
<span class="k">return</span> <span class="k">false</span><span class="p">;</span>
<span class="p">}</span>
<span class="p">}</span>
<span class="sd">/**</span>
<span class="sd"> * 创建目录</span>
<span class="sd"> * </span>
<span class="sd"> * @param context</span>
<span class="sd"> * @param dirName</span>
<span class="sd"> * 文件夹名称</span>
<span class="sd"> * @return</span>
<span class="sd"> */</span>
<span class="k">public</span> <span class="k">static</span> <span class="nx">File</span> <span class="nx">createFileDir</span><span class="p">(</span><span class="nx">Context</span> <span class="nx">context</span><span class="p">,</span> <span class="nx">String</span> <span class="nx">dirName</span><span class="p">)</span> <span class="p">{</span>
<span class="nx">String</span> <span class="nx">filePath</span><span class="p">;</span>
<span class="c1">// 如SD卡已存在,则存储;反之存在data目录下</span>
<span class="k">if</span> <span class="p">(</span><span class="nx">hasSdcard</span><span class="p">())</span> <span class="p">{</span>
<span class="c1">// SD卡路径</span>
<span class="nx">filePath</span> <span class="o">=</span> <span class="nx">Environment</span><span class="o">.</span><span class="nx">getExternalStorageDirectory</span><span class="p">()</span>
<span class="o">+</span> <span class="nx">File</span><span class="o">.</span><span class="nx">separator</span> <span class="o">+</span> <span class="nx">dirName</span><span class="p">;</span>
<span class="p">}</span> <span class="k">else</span> <span class="p">{</span>
<span class="nx">filePath</span> <span class="o">=</span> <span class="nx">context</span><span class="o">.</span><span class="nx">getCacheDir</span><span class="p">()</span><span class="o">.</span><span class="nx">getPath</span><span class="p">()</span> <span class="o">+</span> <span class="nx">File</span><span class="o">.</span><span class="nx">separator</span>
<span class="o">+</span> <span class="nx">dirName</span><span class="p">;</span>
<span class="p">}</span>
<span class="nx">File</span> <span class="nx">destDir</span> <span class="o">=</span> <span class="k">new</span> <span class="nx">File</span><span class="p">(</span><span class="nx">filePath</span><span class="p">);</span>
<span class="k">if</span> <span class="p">(</span><span class="o">!</span><span class="nx">destDir</span><span class="o">.</span><span class="nx">exists</span><span class="p">())</span> <span class="p">{</span>
<span class="nx">boolean</span> <span class="nx">isCreate</span> <span class="o">=</span> <span class="nx">destDir</span><span class="o">.</span><span class="nx">mkdirs</span><span class="p">();</span>
<span class="nx">Log</span><span class="o">.</span><span class="nx">i</span><span class="p">(</span><span class="nx">Util_LOG</span><span class="p">,</span> <span class="nx">filePath</span> <span class="o">+</span> <span class="s2">" has created. "</span> <span class="o">+</span> <span class="nx">isCreate</span><span class="p">);</span>
<span class="p">}</span>
<span class="k">return</span> <span class="nx">destDir</span><span class="p">;</span>
<span class="p">}</span>
<span class="sd">/**</span>
<span class="sd"> * 删除文件(若为目录,则递归删除子目录和文件)</span>
<span class="sd"> * </span>
<span class="sd"> * @param file</span>
<span class="sd"> * @param delThisPath</span>
<span class="sd"> * true代表删除参数指定file,false代表保留参数指定file</span>
<span class="sd"> */</span>
<span class="k">public</span> <span class="k">static</span> <span class="nx">void</span> <span class="nx">delFile</span><span class="p">(</span><span class="nx">File</span> <span class="nb">file</span><span class="p">,</span> <span class="nx">boolean</span> <span class="nx">delThisPath</span><span class="p">)</span> <span class="p">{</span>
<span class="k">if</span> <span class="p">(</span><span class="o">!</span><span class="nb">file</span><span class="o">.</span><span class="nx">exists</span><span class="p">())</span> <span class="p">{</span>
<span class="k">return</span><span class="p">;</span>
<span class="p">}</span>
<span class="k">if</span> <span class="p">(</span><span class="nb">file</span><span class="o">.</span><span class="nx">isDirectory</span><span class="p">())</span> <span class="p">{</span>
<span class="nx">File</span><span class="p">[]</span> <span class="nx">subFiles</span> <span class="o">=</span> <span class="nb">file</span><span class="o">.</span><span class="nx">listFiles</span><span class="p">();</span>
<span class="k">if</span> <span class="p">(</span><span class="nx">subFiles</span> <span class="o">!=</span> <span class="k">null</span><span class="p">)</span> <span class="p">{</span>
<span class="nx">int</span> <span class="nx">num</span> <span class="o">=</span> <span class="nx">subFiles</span><span class="o">.</span><span class="nx">length</span><span class="p">;</span>
<span class="c1">// 删除子目录和文件</span>
<span class="k">for</span> <span class="p">(</span><span class="nx">int</span> <span class="nx">i</span> <span class="o">=</span> <span class="mi">0</span><span class="p">;</span> <span class="nx">i</span> <span class="o"><</span> <span class="nx">num</span><span class="p">;</span> <span class="nx">i</span><span class="o">++</span><span class="p">)</span> <span class="p">{</span>
<span class="nx">delFile</span><span class="p">(</span><span class="nx">subFiles</span><span class="p">[</span><span class="nx">i</span><span class="p">],</span> <span class="k">true</span><span class="p">);</span>
<span class="p">}</span>
<span class="p">}</span>
<span class="p">}</span>
<span class="k">if</span> <span class="p">(</span><span class="nx">delThisPath</span><span class="p">)</span> <span class="p">{</span>
<span class="nb">file</span><span class="o">.</span><span class="nx">delete</span><span class="p">();</span>
<span class="p">}</span>
<span class="p">}</span>
<span class="sd">/**</span>
<span class="sd"> * 获取文件大小,单位为byte(若为目录,则包括所有子目录和文件)</span>
<span class="sd"> * </span>
<span class="sd"> * @param file</span>
<span class="sd"> * @return</span>
<span class="sd"> */</span>
<span class="k">public</span> <span class="k">static</span> <span class="nx">long</span> <span class="nx">getFileSize</span><span class="p">(</span><span class="nx">File</span> <span class="nb">file</span><span class="p">)</span> <span class="p">{</span>
<span class="nx">long</span> <span class="nx">size</span> <span class="o">=</span> <span class="mi">0</span><span class="p">;</span>
<span class="k">if</span> <span class="p">(</span><span class="nb">file</span><span class="o">.</span><span class="nx">exists</span><span class="p">())</span> <span class="p">{</span>
<span class="k">if</span> <span class="p">(</span><span class="nb">file</span><span class="o">.</span><span class="nx">isDirectory</span><span class="p">())</span> <span class="p">{</span>
<span class="nx">File</span><span class="p">[]</span> <span class="nx">subFiles</span> <span class="o">=</span> <span class="nb">file</span><span class="o">.</span><span class="nx">listFiles</span><span class="p">();</span>
<span class="k">if</span> <span class="p">(</span><span class="nx">subFiles</span> <span class="o">!=</span> <span class="k">null</span><span class="p">)</span> <span class="p">{</span>
<span class="nx">int</span> <span class="nx">num</span> <span class="o">=</span> <span class="nx">subFiles</span><span class="o">.</span><span class="nx">length</span><span class="p">;</span>
<span class="k">for</span> <span class="p">(</span><span class="nx">int</span> <span class="nx">i</span> <span class="o">=</span> <span class="mi">0</span><span class="p">;</span> <span class="nx">i</span> <span class="o"><</span> <span class="nx">num</span><span class="p">;</span> <span class="nx">i</span><span class="o">++</span><span class="p">)</span> <span class="p">{</span>
<span class="nx">size</span> <span class="o">+=</span> <span class="nx">getFileSize</span><span class="p">(</span><span class="nx">subFiles</span><span class="p">[</span><span class="nx">i</span><span class="p">]);</span>
<span class="p">}</span>
<span class="p">}</span>
<span class="p">}</span> <span class="k">else</span> <span class="p">{</span>
<span class="nx">size</span> <span class="o">+=</span> <span class="nb">file</span><span class="o">.</span><span class="nx">length</span><span class="p">();</span>
<span class="p">}</span>
<span class="p">}</span>
<span class="k">return</span> <span class="nx">size</span><span class="p">;</span>
<span class="p">}</span>
<span class="sd">/**</span>
<span class="sd"> * 保存Bitmap到指定目录</span>
<span class="sd"> * </span>
<span class="sd"> * @param dir</span>
<span class="sd"> * 目录</span>
<span class="sd"> * @param fileName</span>
<span class="sd"> * 文件名</span>
<span class="sd"> * @param bitmap</span>
<span class="sd"> * @throws IOException</span>
<span class="sd"> */</span>
<span class="k">public</span> <span class="k">static</span> <span class="nx">void</span> <span class="nx">savaBitmap</span><span class="p">(</span><span class="nx">File</span> <span class="nb">dir</span><span class="p">,</span> <span class="nx">String</span> <span class="nx">fileName</span><span class="p">,</span> <span class="nx">Bitmap</span> <span class="nx">bitmap</span><span class="p">)</span> <span class="p">{</span>
<span class="k">if</span> <span class="p">(</span><span class="nx">bitmap</span> <span class="o">==</span> <span class="k">null</span><span class="p">)</span> <span class="p">{</span>
<span class="k">return</span><span class="p">;</span>
<span class="p">}</span>
<span class="nx">File</span> <span class="nb">file</span> <span class="o">=</span> <span class="k">new</span> <span class="nx">File</span><span class="p">(</span><span class="nb">dir</span><span class="p">,</span> <span class="nx">fileName</span><span class="p">);</span>
<span class="k">try</span> <span class="p">{</span>
<span class="nb">file</span><span class="o">.</span><span class="nx">createNewFile</span><span class="p">();</span>
<span class="nx">FileOutputStream</span> <span class="nx">fos</span> <span class="o">=</span> <span class="k">new</span> <span class="nx">FileOutputStream</span><span class="p">(</span><span class="nb">file</span><span class="p">);</span>
<span class="nx">bitmap</span><span class="o">.</span><span class="nx">compress</span><span class="p">(</span><span class="nx">CompressFormat</span><span class="o">.</span><span class="nx">JPEG</span><span class="p">,</span> <span class="mi">100</span><span class="p">,</span> <span class="nx">fos</span><span class="p">);</span>
<span class="nx">fos</span><span class="o">.</span><span class="nb">flush</span><span class="p">();</span>
<span class="nx">fos</span><span class="o">.</span><span class="nx">close</span><span class="p">();</span>
<span class="p">}</span> <span class="k">catch</span> <span class="p">(</span><span class="nx">IOException</span> <span class="nx">e</span><span class="p">)</span> <span class="p">{</span>
<span class="nx">e</span><span class="o">.</span><span class="nx">printStackTrace</span><span class="p">();</span>
<span class="p">}</span>
<span class="p">}</span>
<span class="sd">/**</span>
<span class="sd"> * 判断某目录下文件是否存在</span>
<span class="sd"> * </span>
<span class="sd"> * @param dir</span>
<span class="sd"> * 目录</span>
<span class="sd"> * @param fileName</span>
<span class="sd"> * 文件名</span>
<span class="sd"> * @return</span>
<span class="sd"> */</span>
<span class="k">public</span> <span class="k">static</span> <span class="nx">boolean</span> <span class="nx">isFileExists</span><span class="p">(</span><span class="nx">File</span> <span class="nb">dir</span><span class="p">,</span> <span class="nx">String</span> <span class="nx">fileName</span><span class="p">)</span> <span class="p">{</span>
<span class="k">return</span> <span class="k">new</span> <span class="nx">File</span><span class="p">(</span><span class="nb">dir</span><span class="p">,</span> <span class="nx">fileName</span><span class="p">)</span><span class="o">.</span><span class="nx">exists</span><span class="p">();</span>
<span class="p">}</span>
<span class="p">}</span>
</pre></div>
<h3>图片下载管理类ImageDownLoader</h3>
<p>ImageDownLoader类主要是图片下载管理,包括缓存管理、异步下载管理等。</p>
<div class="highlight"><pre><span></span><span class="nt">public</span> <span class="nt">class</span> <span class="nt">ImageDownLoader</span> <span class="p">{</span>
<span class="err">private</span> <span class="err">static</span> <span class="err">final</span> <span class="err">String</span> <span class="err">ImageDownLoader_Log</span> <span class="err">=</span> <span class="err">Utils</span>
<span class="err">.makeLogTag(ImageDownLoader.class)</span><span class="p">;</span>
<span class="c">/** 保存正在下载或等待下载的URL和相应失败下载次数(初始为0),防止滚动时多次下载 */</span>
<span class="err">private</span> <span class="err">Hashtable<String,</span> <span class="err">Integer></span> <span class="err">taskCollection</span><span class="p">;</span>
<span class="c">/** 缓存类 */</span>
<span class="err">private</span> <span class="err">LruCache<String,</span> <span class="err">Bitmap></span> <span class="err">lruCache</span><span class="p">;</span>
<span class="c">/** 线程池 */</span>
<span class="err">private</span> <span class="err">ExecutorService</span> <span class="err">threadPool</span><span class="p">;</span>
<span class="c">/** 缓存文件目录 (如无SD卡,则data目录下) */</span>
<span class="err">private</span> <span class="err">File</span> <span class="err">cacheFileDir</span><span class="p">;</span>
<span class="c">/** 缓存文件夹 */</span>
<span class="err">private</span> <span class="err">static</span> <span class="err">final</span> <span class="err">String</span> <span class="err">DIR_CACHE</span> <span class="err">=</span> <span class="err">"cache"</span><span class="p">;</span>
<span class="c">/** 缓存文件夹最大容量限制(10M) */</span>
<span class="err">private</span> <span class="err">static</span> <span class="err">final</span> <span class="err">long</span> <span class="err">DIR_CACHE_LIMIT</span> <span class="err">=</span> <span class="err">10</span> <span class="err">*</span> <span class="err">1024</span> <span class="err">*</span> <span class="err">1024</span><span class="p">;</span>
<span class="c">/** 图片下载失败重试次数 */</span>
<span class="err">private</span> <span class="err">static</span> <span class="err">final</span> <span class="err">int</span> <span class="err">IMAGE_DOWNLOAD_FAIL_TIMES</span> <span class="err">=</span> <span class="err">2</span><span class="p">;</span>
<span class="err">public</span> <span class="err">ImageDownLoader(Context</span> <span class="err">context)</span> <span class="err">{</span>
<span class="err">//</span> <span class="err">获取系统分配给每个应用程序的最大内存</span>
<span class="err">int</span> <span class="err">maxMemory</span> <span class="err">=</span> <span class="err">(int)</span> <span class="err">Runtime.getRuntime().maxMemory()</span><span class="p">;</span>
<span class="err">//</span> <span class="err">给LruCache分配最大内存的1/8</span>
<span class="err">lruCache</span> <span class="err">=</span> <span class="err">new</span> <span class="err">LruCache<String,</span> <span class="err">Bitmap>(maxMemory</span> <span class="err">/</span> <span class="err">8)</span> <span class="err">{</span>
<span class="err">//</span> <span class="err">必须重写此方法,来测量Bitmap的大小</span>
<span class="err">@Override</span>
<span class="err">protected</span> <span class="err">int</span> <span class="err">sizeOf(String</span> <span class="err">key,</span> <span class="err">Bitmap</span> <span class="err">value)</span> <span class="err">{</span>
<span class="err">return</span> <span class="err">value.getRowBytes()</span> <span class="err">*</span> <span class="err">value.getHeight()</span> <span class="err">/</span> <span class="err">1024</span><span class="p">;</span>
<span class="p">}</span>
<span class="err">}</span><span class="o">;</span>
<span class="nt">taskCollection</span> <span class="o">=</span> <span class="nt">new</span> <span class="nt">Hashtable</span><span class="o"><</span><span class="nt">String</span><span class="o">,</span> <span class="nt">Integer</span><span class="o">>();</span>
<span class="o">//</span> <span class="nt">创建线程数</span>
<span class="nt">threadPool</span> <span class="o">=</span> <span class="nt">Executors</span><span class="p">.</span><span class="nc">newFixedThreadPool</span><span class="o">(</span><span class="nt">10</span><span class="o">);</span>
<span class="nt">cacheFileDir</span> <span class="o">=</span> <span class="nt">Utils</span><span class="p">.</span><span class="nc">createFileDir</span><span class="o">(</span><span class="nt">context</span><span class="o">,</span> <span class="nt">DIR_CACHE</span><span class="o">);</span>
<span class="err">}</span>
<span class="c">/**</span>
<span class="c"> * 添加Bitmap到内存缓存</span>
<span class="c"> * </span>
<span class="c"> * @param key</span>
<span class="c"> * @param bitmap</span>
<span class="c"> */</span>
<span class="nt">private</span> <span class="nt">void</span> <span class="nt">addLruCache</span><span class="o">(</span><span class="nt">String</span> <span class="nt">key</span><span class="o">,</span> <span class="nt">Bitmap</span> <span class="nt">bitmap</span><span class="o">)</span> <span class="p">{</span>
<span class="err">if</span> <span class="err">(getBitmapFromMemCache(key)</span> <span class="err">==</span> <span class="err">null</span> <span class="err">&&</span> <span class="err">bitmap</span> <span class="err">!=</span> <span class="err">null)</span> <span class="err">{</span>
<span class="err">lruCache.put(key,</span> <span class="err">bitmap)</span><span class="p">;</span>
<span class="p">}</span>
<span class="err">}</span>
<span class="c">/**</span>
<span class="c"> * 从内存缓存中获取Bitmap</span>
<span class="c"> * </span>
<span class="c"> * @param key</span>
<span class="c"> * @return</span>
<span class="c"> */</span>
<span class="nt">private</span> <span class="nt">Bitmap</span> <span class="nt">getBitmapFromMemCache</span><span class="o">(</span><span class="nt">String</span> <span class="nt">key</span><span class="o">)</span> <span class="p">{</span>
<span class="err">return</span> <span class="err">lruCache.get(key)</span><span class="p">;</span>
<span class="p">}</span>
<span class="c">/**</span>
<span class="c"> * 异步下载图片,并按指定宽度和高度压缩图片</span>
<span class="c"> * </span>
<span class="c"> * @param url</span>
<span class="c"> * @param width</span>
<span class="c"> * @param height</span>
<span class="c"> * @param listener</span>
<span class="c"> * 图片下载完成后调用接口</span>
<span class="c"> */</span>
<span class="nt">public</span> <span class="nt">void</span> <span class="nt">loadImage</span><span class="o">(</span><span class="nt">final</span> <span class="nt">String</span> <span class="nt">url</span><span class="o">,</span> <span class="nt">final</span> <span class="nt">int</span> <span class="nt">width</span><span class="o">,</span> <span class="nt">final</span> <span class="nt">int</span> <span class="nt">height</span><span class="o">,</span>
<span class="nt">AsyncImageLoaderListener</span> <span class="nt">listener</span><span class="o">)</span> <span class="p">{</span>
<span class="err">Log.i(ImageDownLoader_Log,</span> <span class="err">"</span><span class="n">download</span><span class="p">:</span><span class="s2">" + url);</span>
<span class="s2"> final ImageHandler handler = new ImageHandler(listener);</span>
<span class="s2"> Runnable runnable = new Runnable() {</span>
<span class="s2"> @Override</span>
<span class="s2"> public void run() {</span>
<span class="s2"> Bitmap bitmap = downloadImage(url, width, height);</span>
<span class="s2"> Message msg = handler.obtainMessage();</span>
<span class="s2"> msg.obj = bitmap;</span>
<span class="s2"> handler.sendMessage(msg);</span>
<span class="s2"> // 将Bitmap 加入内存缓存</span>
<span class="s2"> addLruCache(url, bitmap);</span>
<span class="s2"> // 加入文件缓存前,需判断缓存目录大小是否超过限制,超过则清空缓存再加入</span>
<span class="s2"> long cacheFileSize = Utils.getFileSize(cacheFileDir);</span>
<span class="s2"> if (cacheFileSize > DIR_CACHE_LIMIT) {</span>
<span class="s2"> Log.i(ImageDownLoader_Log, cacheFileDir</span>
<span class="s2"> + "</span> <span class="k">size</span> <span class="n">has</span> <span class="n">exceed</span> <span class="n">limit</span><span class="o">.</span><span class="s2">" + cacheFileSize);</span>
<span class="s2"> Utils.delFile(cacheFileDir, false);</span>
<span class="s2"> taskCollection.clear();</span>
<span class="s2"> }</span>
<span class="s2"> // 缓存文件名称( 替换url中非字母和非数字的字符,防止系统误认为文件路径)</span>
<span class="s2"> String urlKey = url.replaceAll("</span><span class="cp">[</span><span class="p">^</span><span class="o">\\</span><span class="nx">w</span><span class="cp">]</span><span class="s2">", "");</span>
<span class="s2"> // 将Bitmap加入文件缓存</span>
<span class="s2"> Utils.savaBitmap(cacheFileDir, urlKey, bitmap);</span>
<span class="s2"> }</span>
<span class="s2"> };</span>
<span class="s2"> // 记录该url,防止滚动时多次下载,0代表该url下载失败次数</span>
<span class="s2"> taskCollection.put(url, 0);</span>
<span class="s2"> threadPool.execute(runnable);</span>
<span class="s2"> }</span>
<span class="s2"> /**</span>
<span class="s2"> * 获取Bitmap, 若内存缓存为空,则去文件缓存中获取</span>
<span class="s2"> * </span>
<span class="s2"> * @param url</span>
<span class="s2"> * @return 若缓存中没找到,则返回null</span>
<span class="s2"> */</span>
<span class="s2"> public Bitmap getBitmapCache(String url) {</span>
<span class="s2"> // 去处url中特殊字符作为文件缓存的名称</span>
<span class="s2"> String urlKey = url.replaceAll("</span><span class="cp">[</span><span class="p">^</span><span class="o">\\</span><span class="nx">w</span><span class="cp">]</span><span class="s2">", "");</span>
<span class="s2"> if (getBitmapFromMemCache(url) != null) {</span>
<span class="s2"> return getBitmapFromMemCache(url);</span>
<span class="s2"> } else if (Utils.isFileExists(cacheFileDir, urlKey)</span>
<span class="s2"> && Utils.getFileSize(new File(cacheFileDir, urlKey)) > 0) {</span>
<span class="s2"> // 从文件缓存中获取Bitmap</span>
<span class="s2"> Bitmap bitmap = BitmapFactory.decodeFile(cacheFileDir.getPath()</span>
<span class="s2"> + File.separator + urlKey);</span>
<span class="s2"> // 将Bitmap 加入内存缓存</span>
<span class="s2"> addLruCache(url, bitmap);</span>
<span class="s2"> return bitmap;</span>
<span class="s2"> }</span>
<span class="s2"> return null;</span>
<span class="s2"> }</span>
<span class="s2"> /**</span>
<span class="s2"> * 下载图片,并按指定高度和宽度压缩</span>
<span class="s2"> * </span>
<span class="s2"> * @param url</span>
<span class="s2"> * @param width</span>
<span class="s2"> * @param height</span>
<span class="s2"> * @return</span>
<span class="s2"> */</span>
<span class="s2"> private Bitmap downloadImage(String url, int width, int height) {</span>
<span class="s2"> Bitmap bitmap = null;</span>
<span class="s2"> HttpClient httpClient = new DefaultHttpClient();</span>
<span class="s2"> try {</span>
<span class="s2"> httpClient.getParams().setParameter(</span>
<span class="s2"> CoreProtocolPNames.PROTOCOL_VERSION, HttpVersion.HTTP_1_1);</span>
<span class="s2"> HttpPost httpPost = new HttpPost(url);</span>
<span class="s2"> HttpResponse httpResponse = httpClient.execute(httpPost);</span>
<span class="s2"> if (httpResponse.getStatusLine().getStatusCode() == HttpStatus.SC_OK) {</span>
<span class="s2"> HttpEntity entity = httpResponse.getEntity();</span>
<span class="s2"> //解决缩放大图时出现SkImageDecoder::Factory returned null错误</span>
<span class="s2"> byte</span><span class="cp">[]</span><span class="s2"> byteIn = EntityUtils.toByteArray(entity);</span>
<span class="s2"> BitmapFactory.Options bmpFactoryOptions = new BitmapFactory.Options();</span>
<span class="s2"> bmpFactoryOptions.inJustDecodeBounds = true;</span>
<span class="s2"> BitmapFactory.decodeByteArray(byteIn, 0, byteIn.length,</span>
<span class="s2"> bmpFactoryOptions);</span>
<span class="s2"> int heightRatio = (int) Math.ceil(bmpFactoryOptions.outHeight</span>
<span class="s2"> / height);</span>
<span class="s2"> int widthRatio = (int) Math.ceil(bmpFactoryOptions.outWidth</span>
<span class="s2"> / width);</span>
<span class="s2"> if (heightRatio > 1 && widthRatio > 1) {</span>
<span class="s2"> bmpFactoryOptions.inSampleSize = heightRatio > widthRatio ? heightRatio</span>
<span class="s2"> : widthRatio;</span>
<span class="s2"> }</span>
<span class="s2"> bmpFactoryOptions.inJustDecodeBounds = false;</span>
<span class="s2"> bitmap = BitmapFactory.decodeByteArray(byteIn, 0,</span>
<span class="s2"> byteIn.length, bmpFactoryOptions);</span>
<span class="s2"> }</span>
<span class="s2"> } catch (ClientProtocolException e) {</span>
<span class="s2"> e.printStackTrace();</span>
<span class="s2"> } catch (ConnectTimeoutException e) {</span>
<span class="s2"> e.printStackTrace();</span>
<span class="s2"> } catch (Exception e) {</span>
<span class="s2"> e.printStackTrace();</span>
<span class="s2"> } finally {</span>
<span class="s2"> if (httpClient != null && httpClient.getConnectionManager() != null) {</span>
<span class="s2"> httpClient.getConnectionManager().shutdown();</span>
<span class="s2"> }</span>
<span class="s2"> }</span>
<span class="s2"> // 下载失败,再重新下载</span>
<span class="s2"> // 本例是图片下载失败则再次下载,可根据需要改变,比如记录下载失败的图片URL,在某个时刻再次下载</span>
<span class="s2"> if (taskCollection.get(url) != null) {</span>
<span class="s2"> int times = taskCollection.get(url);</span>
<span class="s2"> if (bitmap == null</span>
<span class="s2"> && times < IMAGE_DOWNLOAD_FAIL_TIMES) {</span>
<span class="s2"> times++;</span>
<span class="s2"> taskCollection.put(url, times);</span>
<span class="s2"> bitmap = downloadImage(url, width, height);</span>
<span class="s2"> Log.i(ImageDownLoader_Log, "</span><span class="n">Re-download</span> <span class="s2">" + url + "</span><span class="o">:</span><span class="err">"</span> <span class="o">+</span> <span class="n">times</span><span class="p">);</span>
<span class="p">}</span>
<span class="err">}</span>
<span class="nt">return</span> <span class="nt">bitmap</span><span class="o">;</span>
<span class="err">}</span>
<span class="c">/**</span>
<span class="c"> * 取消正在下载的任务</span>
<span class="c"> */</span>
<span class="nt">public</span> <span class="nt">synchronized</span> <span class="nt">void</span> <span class="nt">cancelTasks</span><span class="o">()</span> <span class="p">{</span>
<span class="err">if</span> <span class="err">(threadPool</span> <span class="err">!=</span> <span class="err">null)</span> <span class="err">{</span>
<span class="err">threadPool.shutdownNow()</span><span class="p">;</span>
<span class="err">threadPool</span> <span class="err">=</span> <span class="err">null</span><span class="p">;</span>
<span class="p">}</span>
<span class="err">}</span>
<span class="c">/**</span>
<span class="c"> * 获取任务列表</span>
<span class="c"> * </span>
<span class="c"> * @return</span>
<span class="c"> */</span>
<span class="nt">public</span> <span class="nt">Hashtable</span><span class="o"><</span><span class="nt">String</span><span class="o">,</span> <span class="nt">Integer</span><span class="o">></span> <span class="nt">getTaskCollection</span><span class="o">()</span> <span class="p">{</span>
<span class="err">return</span> <span class="err">taskCollection</span><span class="p">;</span>
<span class="p">}</span>
<span class="c">/** 异步加载图片接口 */</span>
<span class="nt">public</span> <span class="nt">interface</span> <span class="nt">AsyncImageLoaderListener</span> <span class="p">{</span>
<span class="err">void</span> <span class="err">onImageLoader(Bitmap</span> <span class="err">bitmap)</span><span class="p">;</span>
<span class="p">}</span>
<span class="c">/** 异步加载完成后,图片处理 */</span>
<span class="nt">static</span> <span class="nt">class</span> <span class="nt">ImageHandler</span> <span class="nt">extends</span> <span class="nt">Handler</span> <span class="p">{</span>
<span class="err">private</span> <span class="err">AsyncImageLoaderListener</span> <span class="err">listener</span><span class="p">;</span>
<span class="err">public</span> <span class="err">ImageHandler(AsyncImageLoaderListener</span> <span class="err">listener)</span> <span class="err">{</span>
<span class="err">this.listener</span> <span class="err">=</span> <span class="err">listener</span><span class="p">;</span>
<span class="p">}</span>
<span class="p">@</span><span class="k">Override</span>
<span class="nt">public</span> <span class="nt">void</span> <span class="nt">handleMessage</span><span class="o">(</span><span class="nt">Message</span> <span class="nt">msg</span><span class="o">)</span> <span class="p">{</span>
<span class="nt">super</span><span class="p">.</span><span class="nc">handleMessage</span><span class="o">(</span><span class="nt">msg</span><span class="o">);</span>
<span class="nt">listener</span><span class="p">.</span><span class="nc">onImageLoader</span><span class="o">((</span><span class="nt">Bitmap</span><span class="o">)</span> <span class="nt">msg</span><span class="p">.</span><span class="nc">obj</span><span class="o">);</span>
<span class="p">}</span>
<span class="err">}</span>
<span class="err">}</span>
</pre></div>
<h3>GridView适配器类ImageGridViewAdapter</h3>
<p>该类为GridView适配器,并实现滚动监听等的功能。</p>
<div class="highlight"><pre><span></span>public class ImageGridViewAdapter extends BaseAdapter implements OnScrollListener {
/** 数据源 */
private List<String> data;
/** 图片下载类 */
private ImageDownLoader loader;
/** 判定是否第一次加载 */
private boolean isFirstEnter = true;
/** 第一张可见Item下标 */
private int firstVisibleItem;
/** 每屏Item可见数 */
private int visibleItemCount;
/** GridView实例 */
private GridView gridView;
private Context context;
public ImageGridViewAdapter(Context context, GridView gridView, List<String> data) {
this.context = context;
this.gridView = gridView;
this.data = data;
loader = new ImageDownLoader(context);
this.gridView.setOnScrollListener(this);
}
@Override
public int getCount() {
return data.size();
}
@Override
public Object getItem(int position) {
return data.get(position);
}
@Override
public long getItemId(int position) {
return position;
}
@Override
public View getView(int position, View convertView, ViewGroup parent) {
String url = data.get(position);
if (convertView == null) {
convertView = LayoutInflater.from(context).inflate(
R.layout.photo_item, null);
}
ImageView imageView = (ImageView) convertView.findViewById(R.id.photo);
// 设置Tag,保证异步加载图片不乱序
imageView.setTag(url);
setImageView(imageView, url);
return convertView;
}
private void setImageView(ImageView imageView, String url) {
Bitmap bitmap = loader.getBitmapCache(url);
if (bitmap != null) {
imageView.setImageBitmap(bitmap);
} else {
imageView.setImageResource(R.drawable.empty_photo);
}
}
@Override
public void onScrollStateChanged(AbsListView view, int scrollState) {
// 当停止滚动时,加载图片
if (scrollState == SCROLL_STATE_IDLE) {
loadImage(firstVisibleItem, visibleItemCount);
}
}
@Override
public void onScroll(AbsListView view, int firstVisibleItem,
int visibleItemCount, int totalItemCount) {
this.firstVisibleItem = firstVisibleItem;
this.visibleItemCount = visibleItemCount;
if (isFirstEnter && visibleItemCount > 0) {
loadImage(firstVisibleItem, visibleItemCount);
isFirstEnter = false;
}
}
/**
* 加载图片,若缓存中没有,则根据url下载
*
* @param firstVisibleItem
* @param visibleItemCount
*/
private void loadImage(int firstVisibleItem, int visibleItemCount) {
Bitmap bitmap = null;
for (int i = firstVisibleItem; i < firstVisibleItem + visibleItemCount; i++) {
String url = data.get(i);
final ImageView imageView = (ImageView) gridView
.findViewWithTag(url);
bitmap = loader.getBitmapCache(url);
if (bitmap != null) {
imageView.setImageBitmap(bitmap);
} else {
// 防止滚动时多次下载
if (loader.getTaskCollection().containsKey(url)) {
continue;
}
imageView.setImageDrawable(context.getResources().getDrawable(
R.drawable.empty_photo));
loader.loadImage(url, imageView.getWidth(),
imageView.getHeight(),
new ImageDownLoader.AsyncImageLoaderListener() {
@Override
public void onImageLoader(Bitmap bitmap) {
if (imageView != null && bitmap != null) {
imageView.setImageBitmap(bitmap);
}
}
});
}
}
}
/**
* 取消下载任务
*/
public void cancelTasks() {
loader.cancelTasks();
}
}
</pre></div>
<p>最后的最后,别忘加上权限,并附效果图如下:</p>
<div class="highlight"><pre><span></span><uses-permission android:name="android.permission.INTERNET" />
<uses-permission android:name="android.permission.MOUNT_UNMOUNT_FILESYSTEMS" />
<uses-permission android:name="android.permission.WRITE_EXTERNAL_STORAGE" />
</pre></div>
<p><img alt="upload" src="/images/download-images.png"></p>
<p>有疑问或者觉得不对的地方还请指正,谢谢。</p>
<p><a href="http://download.csdn.net/detail/q541959443/7692557" target="_blank">ImagesDownLoad源码下载:DEMO</a></p>Android使用HttpClient实现文件上传到PHP服务器,并监控进度2014-07-29T10:35:00+08:002014-07-29T10:35:00+08:00xymelontag:www.xycoding.com,2014-07-29:/articles/2014/07/29/android-httpclient-upload/<p>上一篇文章<a href="http://www.xycoding.com/articles/2014/07/28/android-httpclient-download/">Android使用HttpClient实现下载,并监控进度</a>,本文继续讲解实现上传进度监控,原理其实一样,只不过这次重载的是<code>FilterOutputStream</code>,服务器端使用PHP进行文件的接收。好记性不如烂笔头,方便以后查阅,不当之处,还请指正。</p>
<p>上一篇文章<a href="http://www.xycoding.com/articles/2014/07/28/android-httpclient-download/">Android使用HttpClient实现下载,并监控进度</a>,本文继续讲解实现上传进度监控,原理其实一样,只不过这次重载的是<code>FilterOutputStream</code>,服务器端使用PHP进行文件的接收。好记性不如烂笔头,方便以后查阅,不当之处,还请指正。</p>
<h3>服务器端PHP</h3>
<p>废话不多说,直接上代码:</p>
<div class="highlight"><pre><span></span><span class="cp"><?php</span>
<span class="nv">$target_path</span> <span class="o">=</span> <span class="s2">"./tmp/"</span><span class="p">;</span><span class="c1">//接收文件目录 </span>
<span class="nv">$target_path</span> <span class="o">=</span> <span class="nv">$target_path</span><span class="o">.</span><span class="p">(</span><span class="nv">$_FILES</span><span class="p">[</span><span class="s1">'file'</span><span class="p">][</span><span class="s1">'name'</span><span class="p">]);</span>
<span class="nv">$target_path</span> <span class="o">=</span> <span class="nb">iconv</span><span class="p">(</span><span class="s2">"UTF-8"</span><span class="p">,</span><span class="s2">"gb2312"</span><span class="p">,</span> <span class="nv">$target_path</span><span class="p">);</span>
<span class="k">if</span><span class="p">(</span><span class="nb">move_uploaded_file</span><span class="p">(</span><span class="nv">$_FILES</span><span class="p">[</span><span class="s1">'file'</span><span class="p">][</span><span class="s1">'tmp_name'</span><span class="p">],</span> <span class="nv">$target_path</span><span class="p">))</span> <span class="p">{</span>
<span class="k">echo</span> <span class="s2">"The file "</span><span class="o">.</span><span class="p">(</span> <span class="nv">$_FILES</span><span class="p">[</span><span class="s1">'file'</span><span class="p">][</span><span class="s1">'name'</span><span class="p">])</span><span class="o">.</span><span class="s2">" has been uploaded."</span><span class="p">;</span>
<span class="p">}</span><span class="k">else</span><span class="p">{</span>
<span class="k">echo</span> <span class="s2">"There was an error uploading the file, please try again! Error Code: "</span><span class="o">.</span><span class="nv">$_FILES</span><span class="p">[</span><span class="s1">'file'</span><span class="p">][</span><span class="s1">'error'</span><span class="p">];</span>
<span class="p">}</span>
<span class="cp">?></span>
</pre></div>
<h3>监控进度实现</h3>
<p>首先定义监听器接口,这和上文中是一样的,如下所示:</p>
<div class="highlight"><pre><span></span><span class="cm">/**</span>
<span class="cm"> * 进度监听器接口</span>
<span class="cm"> */</span><span class="w"></span>
public<span class="w"> </span>interface<span class="w"> </span>ProgressListener<span class="w"> </span><span class="err">{</span><span class="w"></span>
<span class="w"> </span>public<span class="w"> </span>void<span class="w"> </span>transferred<span class="o">(</span>long<span class="w"> </span>transferedBytes<span class="o">)</span><span class="err">;</span><span class="w"></span>
<span class="err">}</span><span class="w"></span>
</pre></div>
<p>实现监控进度的关键部分就在于记录已传输字节数,所以我们需重载<code>FilterOutputStream</code>,重写其中的关键方法,实现进度监听的功能,如下所示,本例中首先重载的是<code>HttpEntityWrapper</code>,顾名思义,就是将需发送的<code>HttpEntity</code>打包,以便计算总字节数,代码如下:</p>
<div class="highlight"><pre><span></span><span class="cm">/**</span>
<span class="cm"> * ProgressOutHttpEntity:输出流(OutputStream)时记录已发送字节数</span>
<span class="cm"> * </span>
<span class="cm"> * @author Cow</span>
<span class="cm"> * </span>
<span class="cm"> */</span><span class="w"></span>
public<span class="w"> </span>class<span class="w"> </span>ProgressOutHttpEntity<span class="w"> </span>extends<span class="w"> </span>HttpEntityWrapper<span class="w"> </span><span class="err">{</span><span class="w"></span>
<span class="w"> </span>private<span class="w"> </span>final<span class="w"> </span>ProgressListener<span class="w"> </span>listener<span class="err">;</span><span class="w"></span>
<span class="w"> </span>public<span class="w"> </span>ProgressOutHttpEntity<span class="o">(</span>final<span class="w"> </span>HttpEntity<span class="w"> </span>entity<span class="o">,</span><span class="w"></span>
<span class="w"> </span>final<span class="w"> </span>ProgressListener<span class="w"> </span>listener<span class="o">)</span><span class="w"> </span><span class="err">{</span><span class="w"></span>
<span class="w"> </span>super<span class="o">(</span>entity<span class="o">)</span><span class="err">;</span><span class="w"></span>
<span class="w"> </span>this<span class="o">.</span>listener<span class="w"> </span><span class="o">=</span><span class="w"> </span>listener<span class="err">;</span><span class="w"></span>
<span class="w"> </span><span class="err">}</span><span class="w"></span>
<span class="w"> </span>public<span class="w"> </span>static<span class="w"> </span>class<span class="w"> </span>CountingOutputStream<span class="w"> </span>extends<span class="w"> </span>FilterOutputStream<span class="w"> </span><span class="err">{</span><span class="w"></span>
<span class="w"> </span>private<span class="w"> </span>final<span class="w"> </span>ProgressListener<span class="w"> </span>listener<span class="err">;</span><span class="w"></span>
<span class="w"> </span>private<span class="w"> </span>long<span class="w"> </span>transferred<span class="err">;</span><span class="w"></span>
<span class="w"> </span>CountingOutputStream<span class="o">(</span>final<span class="w"> </span>OutputStream<span class="w"> </span>out<span class="o">,</span><span class="w"></span>
<span class="w"> </span>final<span class="w"> </span>ProgressListener<span class="w"> </span>listener<span class="o">)</span><span class="w"> </span><span class="err">{</span><span class="w"></span>
<span class="w"> </span>super<span class="o">(</span>out<span class="o">)</span><span class="err">;</span><span class="w"></span>
<span class="w"> </span>this<span class="o">.</span>listener<span class="w"> </span><span class="o">=</span><span class="w"> </span>listener<span class="err">;</span><span class="w"></span>
<span class="w"> </span>this<span class="o">.</span>transferred<span class="w"> </span><span class="o">=</span><span class="w"> </span><span class="m">0</span><span class="err">;</span><span class="w"></span>
<span class="w"> </span><span class="err">}</span><span class="w"></span>
<span class="w"> </span><span class="err">@</span>Override<span class="w"></span>
<span class="w"> </span>public<span class="w"> </span>void<span class="w"> </span>write<span class="o">(</span>final<span class="w"> </span>byte<span class="err">[]</span><span class="w"> </span>b<span class="o">,</span><span class="w"> </span>final<span class="w"> </span>int<span class="w"> </span><span class="kr">off</span><span class="o">,</span><span class="w"> </span>final<span class="w"> </span>int<span class="w"> </span>len<span class="o">)</span><span class="w"></span>
<span class="w"> </span>throws<span class="w"> </span>IOException<span class="w"> </span><span class="err">{</span><span class="w"></span>
<span class="w"> </span><span class="o">//</span><span class="w"> </span>NO<span class="o">,</span><span class="w"> </span>double<span class="o">-</span>counting<span class="o">,</span><span class="w"> </span>as<span class="w"> </span>super<span class="o">.</span>write<span class="o">(</span>byte<span class="err">[]</span><span class="o">,</span><span class="w"> </span>int<span class="o">,</span><span class="w"> </span>int<span class="o">)</span><span class="w"></span>
<span class="w"> </span><span class="o">//</span><span class="w"> </span>delegates<span class="w"> </span><span class="kr">to</span><span class="w"> </span>write<span class="o">(</span>int<span class="o">).</span><span class="w"></span>
<span class="w"> </span><span class="o">//</span><span class="w"> </span>super<span class="o">.</span>write<span class="o">(</span>b<span class="o">,</span><span class="w"> </span><span class="kr">off</span><span class="o">,</span><span class="w"> </span>len<span class="o">)</span><span class="err">;</span><span class="w"></span>
<span class="w"> </span>out<span class="o">.</span>write<span class="o">(</span>b<span class="o">,</span><span class="w"> </span><span class="kr">off</span><span class="o">,</span><span class="w"> </span>len<span class="o">)</span><span class="err">;</span><span class="w"></span>
<span class="w"> </span>this<span class="o">.</span>transferred<span class="w"> </span><span class="o">+=</span><span class="w"> </span>len<span class="err">;</span><span class="w"></span>
<span class="w"> </span>this<span class="o">.</span>listener<span class="o">.</span>transferred<span class="o">(</span>this<span class="o">.</span>transferred<span class="o">)</span><span class="err">;</span><span class="w"></span>
<span class="w"> </span><span class="err">}</span><span class="w"></span>
<span class="w"> </span><span class="err">@</span>Override<span class="w"></span>
<span class="w"> </span>public<span class="w"> </span>void<span class="w"> </span>write<span class="o">(</span>final<span class="w"> </span>int<span class="w"> </span>b<span class="o">)</span><span class="w"> </span>throws<span class="w"> </span>IOException<span class="w"> </span><span class="err">{</span><span class="w"></span>
<span class="w"> </span>out<span class="o">.</span>write<span class="o">(</span>b<span class="o">)</span><span class="err">;</span><span class="w"></span>
<span class="w"> </span>this<span class="o">.</span>transferred<span class="o">++</span><span class="err">;</span><span class="w"></span>
<span class="w"> </span>this<span class="o">.</span>listener<span class="o">.</span>transferred<span class="o">(</span>this<span class="o">.</span>transferred<span class="o">)</span><span class="err">;</span><span class="w"></span>
<span class="w"> </span><span class="err">}</span><span class="w"></span>
<span class="w"> </span><span class="err">}</span><span class="w"></span>
<span class="w"> </span><span class="err">@</span>Override<span class="w"></span>
<span class="w"> </span>public<span class="w"> </span>void<span class="w"> </span>writeTo<span class="o">(</span>final<span class="w"> </span>OutputStream<span class="w"> </span>out<span class="o">)</span><span class="w"> </span>throws<span class="w"> </span>IOException<span class="w"> </span><span class="err">{</span><span class="w"></span>
<span class="w"> </span>this<span class="o">.</span>wrappedEntity<span class="o">.</span>writeTo<span class="o">(</span>out<span class="w"> </span>instanceof<span class="w"> </span>CountingOutputStream<span class="w"> </span><span class="err">?</span><span class="w"> </span><span class="nl">out</span><span class="w"></span>
<span class="w"> </span><span class="o">:</span><span class="w"> </span>new<span class="w"> </span>CountingOutputStream<span class="o">(</span>out<span class="o">,</span><span class="w"> </span>this<span class="o">.</span>listener<span class="o">))</span><span class="err">;</span><span class="w"></span>
<span class="w"> </span><span class="err">}</span><span class="w"></span>
<span class="err">}</span><span class="w"></span>
</pre></div>
<p>最后就是使用上述实现的类和Httpclient进行上传并显示进度的功能,非常简单,代码如下,使用AsyncTask异步上传。</p>
<div class="highlight"><pre><span></span><span class="nt">public</span> <span class="nt">class</span> <span class="nt">FileUploadAsyncTask</span> <span class="nt">extends</span> <span class="nt">AsyncTask</span><span class="o"><</span><span class="nt">File</span><span class="o">,</span> <span class="nt">Integer</span><span class="o">,</span> <span class="nt">String</span><span class="o">></span> <span class="p">{</span>
<span class="err">private</span> <span class="err">String</span> <span class="err">url</span> <span class="err">=</span> <span class="err">"</span><span class="n">http</span><span class="p">:</span><span class="o">//</span><span class="mf">192.168.83.213</span><span class="o">/</span><span class="n">receive_file</span><span class="o">.</span><span class="n">php</span><span class="s2">";</span>
<span class="s2"> private Context context;</span>
<span class="s2"> private ProgressDialog pd;</span>
<span class="s2"> private long totalSize;</span>
<span class="s2"> public FileUploadAsyncTask(Context context) {</span>
<span class="s2"> this.context = context;</span>
<span class="s2"> }</span>
<span class="s2"> @Override</span>
<span class="s2"> protected void onPreExecute() {</span>
<span class="s2"> pd = new ProgressDialog(context);</span>
<span class="s2"> pd.setProgressStyle(ProgressDialog.STYLE_HORIZONTAL);</span>
<span class="s2"> pd.setMessage("</span><span class="err">上传中</span><span class="o">....</span><span class="s2">");</span>
<span class="s2"> pd.setCancelable(false);</span>
<span class="s2"> pd.show();</span>
<span class="s2"> }</span>
<span class="s2"> @Override</span>
<span class="s2"> protected String doInBackground(File... params) {</span>
<span class="s2"> // 保存需上传文件信息</span>
<span class="s2"> MultipartEntityBuilder entitys = MultipartEntityBuilder.create();</span>
<span class="s2"> entitys.setMode(HttpMultipartMode.BROWSER_COMPATIBLE);</span>
<span class="s2"> entitys.setCharset(Charset.forName(HTTP.UTF_8));</span>
<span class="s2"> File file = params</span><span class="cp">[</span><span class="mi">0</span><span class="cp">]</span><span class="s2">;</span>
<span class="s2"> entitys.addPart("</span><span class="n">file</span><span class="s2">", new FileBody(file));</span>
<span class="s2"> HttpEntity httpEntity = entitys.build();</span>
<span class="s2"> totalSize = httpEntity.getContentLength();</span>
<span class="s2"> ProgressOutHttpEntity progressHttpEntity = new ProgressOutHttpEntity(</span>
<span class="s2"> httpEntity, new ProgressListener() {</span>
<span class="s2"> @Override</span>
<span class="s2"> public void transferred(long transferedBytes) {</span>
<span class="s2"> publishProgress((int) (100 * transferedBytes / totalSize));</span>
<span class="s2"> }</span>
<span class="s2"> });</span>
<span class="s2"> return uploadFile(url, progressHttpEntity);</span>
<span class="s2"> }</span>
<span class="s2"> @Override</span>
<span class="s2"> protected void onProgressUpdate(Integer... progress) {</span>
<span class="s2"> pd.setProgress((int) (progress</span><span class="cp">[</span><span class="mi">0</span><span class="cp">]</span><span class="s2">));</span>
<span class="s2"> }</span>
<span class="s2"> @Override</span>
<span class="s2"> protected void onPostExecute(String result) {</span>
<span class="s2"> pd.dismiss();</span>
<span class="s2"> Toast.makeText(context, result, Toast.LENGTH_SHORT).show();</span>
<span class="s2"> }</span>
<span class="s2"> /**</span>
<span class="s2"> * 上传文件到服务器</span>
<span class="s2"> * </span>
<span class="s2"> * @param url</span>
<span class="s2"> * 服务器地址</span>
<span class="s2"> * @param entity</span>
<span class="s2"> * 文件</span>
<span class="s2"> * @return</span>
<span class="s2"> */</span>
<span class="s2"> public static String uploadFile(String url, ProgressOutHttpEntity entity) {</span>
<span class="s2"> HttpClient httpClient = new DefaultHttpClient();</span>
<span class="s2"> httpClient.getParams().setParameter(</span>
<span class="s2"> CoreProtocolPNames.PROTOCOL_VERSION, HttpVersion.HTTP_1_1);</span>
<span class="s2"> // 设置连接超时时间</span>
<span class="s2"> httpClient.getParams().setParameter(</span>
<span class="s2"> CoreConnectionPNames.CONNECTION_TIMEOUT, 5000);</span>
<span class="s2"> HttpPost httpPost = new HttpPost(url);</span>
<span class="s2"> httpPost.setEntity(entity);</span>
<span class="s2"> try {</span>
<span class="s2"> HttpResponse httpResponse = httpClient.execute(httpPost);</span>
<span class="s2"> if (httpResponse.getStatusLine().getStatusCode() == HttpStatus.SC_OK) {</span>
<span class="s2"> return "</span><span class="err">文件上传成功</span><span class="s2">";</span>
<span class="s2"> }</span>
<span class="s2"> } catch (ClientProtocolException e) {</span>
<span class="s2"> e.printStackTrace();</span>
<span class="s2"> } catch (ConnectTimeoutException e) {</span>
<span class="s2"> e.printStackTrace();</span>
<span class="s2"> } catch (Exception e) {</span>
<span class="s2"> e.printStackTrace();</span>
<span class="s2"> } finally {</span>
<span class="s2"> if (httpClient != null && httpClient.getConnectionManager() != null) {</span>
<span class="s2"> httpClient.getConnectionManager().shutdown();</span>
<span class="s2"> }</span>
<span class="s2"> }</span>
<span class="s2"> return "</span><span class="err">文件上传失败"</span><span class="p">;</span>
<span class="p">}</span>
<span class="err">}</span>
</pre></div>
<p>最后的最后,上传效果图如下:</p>
<p><img alt="upload" src="/images/image-upload.png"></p>
<p>有疑问或者觉得不对的地方还请指正,谢谢。</p>
<p><a href="https://github.com/cowfighting/ImageWithProgress" target="_blank">GitHub源码下载:DEMO</a></p>Android使用HttpClient实现下载,并监控进度2014-07-28T19:50:00+08:002014-07-28T19:50:00+08:00xymelontag:www.xycoding.com,2014-07-28:/articles/2014/07/28/android-httpclient-download/<p>Android程序开发中使用网络上传下载是必不可少的,前不久使用<a href="http://hc.apache.org/">Apache Httpclient</a>组件进行图片上传下载,并监控进度等功能的了解与实现,并解决了缩放图片时出现<code>SkImageDecoder::Factory returned null</code>错误。本文是在httpcore-4.3.jar,httpmime-4.3.4.jar基础上实现,文章末尾提供的Demo下载中包含了它们。关于上传,请移步<a href="http://www.xycoding.com/articles/2014/07/29/android-httpclient-upload/">Android使用HttpClient实现文件上传到PHP服务器,并监控进度</a>。好记性不如烂笔头,方便以后查阅,不当之处,还请指正。</p>
<p>Android程序开发中使用网络上传下载是必不可少的,前不久使用<a href="http://hc.apache.org/">Apache Httpclient</a>组件进行图片上传下载,并监控进度等功能的了解与实现,并解决了缩放图片时出现<code>SkImageDecoder::Factory returned null</code>错误。本文是在httpcore-4.3.jar,httpmime-4.3.4.jar基础上实现,文章末尾提供的Demo下载中包含了它们。关于上传,请移步<a href="http://www.xycoding.com/articles/2014/07/29/android-httpclient-upload/">Android使用HttpClient实现文件上传到PHP服务器,并监控进度</a>。好记性不如烂笔头,方便以后查阅,不当之处,还请指正。</p>
<h3>监控进度实现</h3>
<p>关于HttpClient的资料,可去其官方查阅。本例简单的使用它进行图片下载,如想实现批量下载图片,请参见<a href="http://www.xycoding.com/articles/2014/07/29/android-async-images-download/">Android异步批量下载图片并缓存</a>。</p>
<p>首先定义监听器接口,如下所示:</p>
<div class="highlight"><pre><span></span><span class="cm">/**</span>
<span class="cm"> * 进度监听器接口</span>
<span class="cm"> */</span><span class="w"></span>
public<span class="w"> </span>interface<span class="w"> </span>ProgressListener<span class="w"> </span><span class="err">{</span><span class="w"></span>
<span class="w"> </span>public<span class="w"> </span>void<span class="w"> </span>transferred<span class="o">(</span>long<span class="w"> </span>transferedBytes<span class="o">)</span><span class="err">;</span><span class="w"></span>
<span class="err">}</span><span class="w"></span>
</pre></div>
<p>然后自定义一个<code>CountingInputStream</code>类继承于<code>FilterInputStream</code>,重写其中的关键方法,实现进度监听的功能,如下所示:</p>
<div class="highlight"><pre><span></span><span class="cm">/**</span>
<span class="cm"> * 重写FilterInputStream,实现进度监听的功能</span>
<span class="cm"> * </span>
<span class="cm"> * @author Cow</span>
<span class="cm"> * </span>
<span class="cm"> */</span><span class="w"></span>
public<span class="w"> </span>class<span class="w"> </span>CountingInputStream<span class="w"> </span>extends<span class="w"> </span>FilterInputStream<span class="w"> </span><span class="err">{</span><span class="w"></span>
<span class="w"> </span>private<span class="w"> </span>final<span class="w"> </span>ProgressListener<span class="w"> </span>listener<span class="err">;</span><span class="w"></span>
<span class="w"> </span>private<span class="w"> </span>long<span class="w"> </span>transferred<span class="err">;</span><span class="w"></span>
<span class="w"> </span>protected<span class="w"> </span>CountingInputStream<span class="o">(</span>final<span class="w"> </span>InputStream<span class="w"> </span>in<span class="o">,</span><span class="w"></span>
<span class="w"> </span>final<span class="w"> </span>ProgressListener<span class="w"> </span>listener<span class="o">)</span><span class="w"> </span><span class="err">{</span><span class="w"></span>
<span class="w"> </span>super<span class="o">(</span>in<span class="o">)</span><span class="err">;</span><span class="w"></span>
<span class="w"> </span>this<span class="o">.</span>listener<span class="w"> </span><span class="o">=</span><span class="w"> </span>listener<span class="err">;</span><span class="w"></span>
<span class="w"> </span>this<span class="o">.</span>transferred<span class="w"> </span><span class="o">=</span><span class="w"> </span><span class="m">0</span><span class="err">;</span><span class="w"></span>
<span class="w"> </span><span class="err">}</span><span class="w"></span>
<span class="w"> </span><span class="err">@</span>Override<span class="w"></span>
<span class="w"> </span>public<span class="w"> </span>int<span class="w"> </span>read<span class="o">()</span><span class="w"> </span>throws<span class="w"> </span>IOException<span class="w"> </span><span class="err">{</span><span class="w"></span>
<span class="w"> </span>int<span class="w"> </span>read<span class="w"> </span><span class="o">=</span><span class="w"> </span>in<span class="o">.</span>read<span class="o">()</span><span class="err">;</span><span class="w"></span>
<span class="w"> </span>readCount<span class="o">(</span>read<span class="o">)</span><span class="err">;</span><span class="w"></span>
<span class="w"> </span><span class="kr">return</span><span class="w"> </span>read<span class="err">;</span><span class="w"></span>
<span class="w"> </span><span class="err">}</span><span class="w"></span>
<span class="w"> </span><span class="err">@</span>Override<span class="w"></span>
<span class="w"> </span>public<span class="w"> </span>int<span class="w"> </span>read<span class="o">(</span>byte<span class="err">[]</span><span class="w"> </span>buffer<span class="o">)</span><span class="w"> </span>throws<span class="w"> </span>IOException<span class="w"> </span><span class="err">{</span><span class="w"></span>
<span class="w"> </span>int<span class="w"> </span>read<span class="w"> </span><span class="o">=</span><span class="w"> </span>in<span class="o">.</span>read<span class="o">(</span>buffer<span class="o">)</span><span class="err">;</span><span class="w"></span>
<span class="w"> </span>readCount<span class="o">(</span>read<span class="o">)</span><span class="err">;</span><span class="w"></span>
<span class="w"> </span><span class="kr">return</span><span class="w"> </span>read<span class="err">;</span><span class="w"></span>
<span class="w"> </span><span class="err">}</span><span class="w"></span>
<span class="w"> </span><span class="err">@</span>Override<span class="w"></span>
<span class="w"> </span>public<span class="w"> </span>int<span class="w"> </span>read<span class="o">(</span>byte<span class="err">[]</span><span class="w"> </span>buffer<span class="o">,</span><span class="w"> </span>int<span class="w"> </span>byteOffset<span class="o">,</span><span class="w"> </span>int<span class="w"> </span>byteCount<span class="o">)</span><span class="w"></span>
<span class="w"> </span>throws<span class="w"> </span>IOException<span class="w"> </span><span class="err">{</span><span class="w"></span>
<span class="w"> </span>int<span class="w"> </span>read<span class="w"> </span><span class="o">=</span><span class="w"> </span>in<span class="o">.</span>read<span class="o">(</span>buffer<span class="o">,</span><span class="w"> </span>byteOffset<span class="o">,</span><span class="w"> </span>byteCount<span class="o">)</span><span class="err">;</span><span class="w"></span>
<span class="w"> </span>readCount<span class="o">(</span>read<span class="o">)</span><span class="err">;</span><span class="w"></span>
<span class="w"> </span><span class="kr">return</span><span class="w"> </span>read<span class="err">;</span><span class="w"></span>
<span class="w"> </span><span class="err">}</span><span class="w"></span>
<span class="w"> </span><span class="err">@</span>Override<span class="w"></span>
<span class="w"> </span>public<span class="w"> </span>long<span class="w"> </span>skip<span class="o">(</span>long<span class="w"> </span>byteCount<span class="o">)</span><span class="w"> </span>throws<span class="w"> </span>IOException<span class="w"> </span><span class="err">{</span><span class="w"></span>
<span class="w"> </span>long<span class="w"> </span>skip<span class="w"> </span><span class="o">=</span><span class="w"> </span>in<span class="o">.</span>skip<span class="o">(</span>byteCount<span class="o">)</span><span class="err">;</span><span class="w"></span>
<span class="w"> </span>readCount<span class="o">(</span>skip<span class="o">)</span><span class="err">;</span><span class="w"></span>
<span class="w"> </span><span class="kr">return</span><span class="w"> </span>skip<span class="err">;</span><span class="w"></span>
<span class="w"> </span><span class="err">}</span><span class="w"></span>
<span class="w"> </span>private<span class="w"> </span>void<span class="w"> </span>readCount<span class="o">(</span>long<span class="w"> </span>read<span class="o">)</span><span class="w"> </span><span class="err">{</span><span class="w"></span>
<span class="w"> </span><span class="kr">if</span><span class="w"> </span><span class="o">(</span>read<span class="w"> </span><span class="o">></span><span class="w"> </span><span class="m">0</span><span class="o">)</span><span class="w"> </span><span class="err">{</span><span class="w"></span>
<span class="w"> </span>this<span class="o">.</span>transferred<span class="w"> </span><span class="o">+=</span><span class="w"> </span>read<span class="err">;</span><span class="w"></span>
<span class="w"> </span>this<span class="o">.</span>listener<span class="o">.</span>transferred<span class="o">(</span>this<span class="o">.</span>transferred<span class="o">)</span><span class="err">;</span><span class="w"></span>
<span class="w"> </span><span class="err">}</span><span class="w"></span>
<span class="w"> </span><span class="err">}</span><span class="w"></span>
<span class="err">}</span><span class="w"></span>
</pre></div>
<p>最后就是使用上述实现的类和Httpclient进行下载并显示进度的功能,非常简单,代码如下,使用AsyncTask异步下载。</p>
<div class="highlight"><pre><span></span><span class="nt">public</span> <span class="nt">class</span> <span class="nt">FileDownLoadAsyncTask</span> <span class="nt">extends</span>
<span class="nt">AsyncTask</span><span class="o"><</span><span class="nt">ImageView</span><span class="o">,</span> <span class="nt">Integer</span><span class="o">,</span> <span class="nt">Bitmap</span><span class="o">></span> <span class="p">{</span>
<span class="err">//</span> <span class="err">图片下载地址</span>
<span class="err">private</span> <span class="err">String</span> <span class="err">url</span> <span class="err">=</span> <span class="err">"</span><span class="n">http</span><span class="p">:</span><span class="o">//</span><span class="n">img0</span><span class="o">.</span><span class="n">bdstatic</span><span class="o">.</span><span class="n">com</span><span class="o">/</span><span class="n">img</span><span class="o">/</span><span class="n">image</span><span class="o">/</span><span class="n">shouye</span><span class="o">/</span><span class="n">leimu</span><span class="o">/</span><span class="n">mingxing</span><span class="o">.</span><span class="n">jpg</span><span class="s2">";</span>
<span class="s2"> private Context context;</span>
<span class="s2"> private ProgressDialog pd;</span>
<span class="s2"> private ImageView image;</span>
<span class="s2"> private int width = 150;</span>
<span class="s2"> private int height = 150;</span>
<span class="s2"> public FileDownLoadAsyncTask(Context context) {</span>
<span class="s2"> this.context = context;</span>
<span class="s2"> }</span>
<span class="s2"> @Override</span>
<span class="s2"> protected void onPreExecute() {</span>
<span class="s2"> pd = new ProgressDialog(context);</span>
<span class="s2"> pd.setProgressStyle(ProgressDialog.STYLE_HORIZONTAL);</span>
<span class="s2"> pd.setMessage("</span><span class="err">下载中</span><span class="o">....</span><span class="s2">");</span>
<span class="s2"> pd.setCancelable(false);</span>
<span class="s2"> pd.show();</span>
<span class="s2"> }</span>
<span class="s2"> /**</span>
<span class="s2"> * 下载图片,并按指定高度和宽度压缩</span>
<span class="s2"> */</span>
<span class="s2"> @Override</span>
<span class="s2"> protected Bitmap doInBackground(ImageView... params) {</span>
<span class="s2"> this.image = params</span><span class="cp">[</span><span class="mi">0</span><span class="cp">]</span><span class="s2">;</span>
<span class="s2"> Bitmap bitmap = null;</span>
<span class="s2"> HttpClient httpClient = new DefaultHttpClient();</span>
<span class="s2"> try {</span>
<span class="s2"> httpClient.getParams().setParameter(</span>
<span class="s2"> CoreProtocolPNames.PROTOCOL_VERSION, HttpVersion.HTTP_1_1);</span>
<span class="s2"> HttpGet httpGet = new HttpGet(url);</span>
<span class="s2"> HttpResponse httpResponse = httpClient.execute(httpGet);</span>
<span class="s2"> if (httpResponse.getStatusLine().getStatusCode() == HttpStatus.SC_OK) {</span>
<span class="s2"> HttpEntity entity = httpResponse.getEntity();</span>
<span class="s2"> final long size = entity.getContentLength();</span>
<span class="s2"> CountingInputStream cis = new CountingInputStream(</span>
<span class="s2"> entity.getContent(), new ProgressListener() {</span>
<span class="s2"> @Override</span>
<span class="s2"> public void transferred(long transferedBytes) {</span>
<span class="s2"> Log.i("</span><span class="n">FileDownLoadAsyncTask</span><span class="s2">", "</span><span class="err">总字节数:</span><span class="s2">" + size</span>
<span class="s2"> + "</span> <span class="err">已下载字节数:</span><span class="s2">" + transferedBytes);</span>
<span class="s2"> publishProgress((int) (100 * transferedBytes / size));</span>
<span class="s2"> }</span>
<span class="s2"> });</span>
<span class="s2"> // 需将Inputstream转化为byte数组,以备decodeByteArray用</span>
<span class="s2"> // 如直接使用decodeStream会将stream破坏,然后第二次decodeStream时,会出现SkImageDecoder::Factory returned null错误</span>
<span class="s2"> // 我试过将获得的Inputstream转化为BufferedInputStream,然后使用mark、reset方法,但是我试了试没成功,不知道为啥,还请成功的各位告知</span>
<span class="s2"> byte</span><span class="cp">[]</span><span class="s2"> byteIn = toByteArray(cis, (int) size);</span>
<span class="s2"> BitmapFactory.Options bmpFactoryOptions = new BitmapFactory.Options();</span>
<span class="s2"> // 第一次decode时,需设置inJustDecodeBounds属性为true,这样系统就会只读取下载图片的属性而不分配空间,并将属性存储在Options中</span>
<span class="s2"> bmpFactoryOptions.inJustDecodeBounds = true;</span>
<span class="s2"> // 第一次decode,获取图片高宽度等属性</span>
<span class="s2"> BitmapFactory.decodeByteArray(byteIn, 0, byteIn.length,</span>
<span class="s2"> bmpFactoryOptions);</span>
<span class="s2"> // 根据显示控件大小获取压缩比率,有效避免OOM</span>
<span class="s2"> int heightRatio = (int) Math.ceil(bmpFactoryOptions.outHeight</span>
<span class="s2"> / height);</span>
<span class="s2"> int widthRatio = (int) Math.ceil(bmpFactoryOptions.outWidth</span>
<span class="s2"> / width);</span>
<span class="s2"> if (heightRatio > 1 && widthRatio > 1) {</span>
<span class="s2"> bmpFactoryOptions.inSampleSize = heightRatio > widthRatio ? heightRatio</span>
<span class="s2"> : widthRatio;</span>
<span class="s2"> }</span>
<span class="s2"> // 第二次decode时,需设置inJustDecodeBounds属性为fasle,系统才会根据传入的BitmapFactory.Options真正的压缩图片并返回</span>
<span class="s2"> bmpFactoryOptions.inJustDecodeBounds = false;</span>
<span class="s2"> bitmap = BitmapFactory.decodeByteArray(byteIn, 0,</span>
<span class="s2"> byteIn.length, bmpFactoryOptions);</span>
<span class="s2"> }</span>
<span class="s2"> } catch (ClientProtocolException e) {</span>
<span class="s2"> e.printStackTrace();</span>
<span class="s2"> } catch (ConnectTimeoutException e) {</span>
<span class="s2"> e.printStackTrace();</span>
<span class="s2"> } catch (Exception e) {</span>
<span class="s2"> e.printStackTrace();</span>
<span class="s2"> } finally {</span>
<span class="s2"> if (httpClient != null && httpClient.getConnectionManager() != null) {</span>
<span class="s2"> httpClient.getConnectionManager().shutdown();</span>
<span class="s2"> }</span>
<span class="s2"> }</span>
<span class="s2"> return bitmap;</span>
<span class="s2"> }</span>
<span class="s2"> @Override</span>
<span class="s2"> protected void onProgressUpdate(Integer... progress) {</span>
<span class="s2"> pd.setProgress((int) (progress</span><span class="cp">[</span><span class="mi">0</span><span class="cp">]</span><span class="s2">));</span>
<span class="s2"> }</span>
<span class="s2"> @Override</span>
<span class="s2"> protected void onPostExecute(Bitmap bm) {</span>
<span class="s2"> pd.dismiss();</span>
<span class="s2"> if (bm != null) {</span>
<span class="s2"> image.setImageBitmap(bm);</span>
<span class="s2"> } else {</span>
<span class="s2"> Toast.makeText(context, "</span><span class="err">图片下载失败"</span><span class="p">,</span> <span class="n">Toast</span><span class="o">.</span><span class="n">LENGTH_SHORT</span><span class="p">)</span><span class="o">.</span><span class="nf">show</span><span class="p">();</span>
<span class="p">}</span>
<span class="err">}</span>
<span class="c">/**</span>
<span class="c"> * InputStream转化为Byte数组</span>
<span class="c"> * </span>
<span class="c"> * @param instream</span>
<span class="c"> * @param contentLength</span>
<span class="c"> * @return</span>
<span class="c"> * @throws IOException</span>
<span class="c"> */</span>
<span class="nt">public</span> <span class="nt">byte</span><span class="cp">[]</span> <span class="nt">toByteArray</span><span class="o">(</span><span class="nt">InputStream</span> <span class="nt">instream</span><span class="o">,</span> <span class="nt">int</span> <span class="nt">contentLength</span><span class="o">)</span>
<span class="nt">throws</span> <span class="nt">IOException</span> <span class="p">{</span>
<span class="err">if</span> <span class="err">(instream</span> <span class="err">==</span> <span class="err">null)</span> <span class="err">{</span>
<span class="err">return</span> <span class="err">null</span><span class="p">;</span>
<span class="p">}</span>
<span class="nt">try</span> <span class="p">{</span>
<span class="err">if</span> <span class="err">(contentLength</span> <span class="err"><</span> <span class="err">0)</span> <span class="err">{</span>
<span class="err">contentLength</span> <span class="err">=</span> <span class="err">4096</span><span class="p">;</span>
<span class="p">}</span>
<span class="nt">final</span> <span class="nt">ByteArrayBuffer</span> <span class="nt">buffer</span> <span class="o">=</span> <span class="nt">new</span> <span class="nt">ByteArrayBuffer</span><span class="o">(</span><span class="nt">contentLength</span><span class="o">);</span>
<span class="nt">final</span> <span class="nt">byte</span><span class="cp">[]</span> <span class="nt">tmp</span> <span class="o">=</span> <span class="nt">new</span> <span class="nt">byte</span><span class="cp">[</span><span class="mi">4096</span><span class="cp">]</span><span class="o">;</span>
<span class="nt">int</span> <span class="nt">l</span><span class="o">;</span>
<span class="nt">while</span> <span class="o">((</span><span class="nt">l</span> <span class="o">=</span> <span class="nt">instream</span><span class="p">.</span><span class="nc">read</span><span class="o">(</span><span class="nt">tmp</span><span class="o">))</span> <span class="o">!=</span> <span class="nt">-1</span><span class="o">)</span> <span class="p">{</span>
<span class="err">buffer.append(tmp,</span> <span class="err">0,</span> <span class="err">l)</span><span class="p">;</span>
<span class="p">}</span>
<span class="nt">return</span> <span class="nt">buffer</span><span class="p">.</span><span class="nc">toByteArray</span><span class="o">();</span>
<span class="err">}</span> <span class="nt">finally</span> <span class="p">{</span>
<span class="err">instream.close()</span><span class="p">;</span>
<span class="p">}</span>
<span class="err">}</span>
<span class="err">}</span>
</pre></div>
<p>最后的最后,下载效果图如下:</p>
<p><img alt="dowmload" src="/images/image-download.png"></p>
<p>有疑问或者觉得不对的地方还请指正,谢谢。</p>
<p><a href="https://github.com/cowfighting/ImageWithProgress" target="_blank">GitHub源码下载:DEMO</a></p>详解android:layout_weight2014-03-28T17:51:00+08:002014-03-28T17:51:00+08:00xymelontag:www.xycoding.com,2014-03-28:/articles/2014/03/28/android-layout-weight/<h3>android:layout_weight</h3>
<p><code>android:weight</code>是线性布局<code>LinearLayout</code>中非常重要的概念,在创造兼容性APP中有不可替代的功劳。它主要作用为分配剩余空间,剩余空间是在线性布局中把子组件所占空间分配完毕后所剩余的部分。每个子组件占剩余空间的比例等于自身所设置参数除以所有子组件所设置参数之和。</p>
<h3>android:layout_weight</h3>
<p><code>android:weight</code>是线性布局<code>LinearLayout</code>中非常重要的概念,在创造兼容性APP中有不可替代的功劳。它主要作用为分配剩余空间,剩余空间是在线性布局中把子组件所占空间分配完毕后所剩余的部分。每个子组件占剩余空间的比例等于自身所设置参数除以所有子组件所设置参数之和。</p>
<p>对于线性布局有<code>horizontal</code>和<code>vertical</code>两个方向,不过剩余空间计算方法都是一样的,以下举例都为<code>android:orientation="horizontal"</code>。</p>
<p>剩余空间计算公式:<strong>剩余空间 = 父组件宽度 - 各组件所设置宽度或所占宽度</strong></p>
<p><strong>子组件所占空间 = 所设置宽度或所占宽度 + 剩余空间 * 子组件所占比例</strong></p>
<p>剩余空间和子组件所占空间与<code>android:layout_width</code>属性设置参数(<code>wrap_content</code>和<code>match_parent</code>)有很大关系,以下分别举例。</p>
<h3>设置参数<code>android:layout_width:"wrap_content"</code></h3>
<p>首先看看如下布局xml:</p>
<div class="highlight"><pre><span></span><span class="cp"><?xml version="1.0" encoding="utf-8"?></span>
<span class="nt"><LinearLayout</span> <span class="na">xmlns:android=</span><span class="s">"http://schemas.android.com/apk/res/android"</span>
<span class="na">android:layout_width=</span><span class="s">"match_parent"</span>
<span class="na">android:layout_height=</span><span class="s">"match_parent"</span>
<span class="na">android:orientation=</span><span class="s">"horizontal"</span>
<span class="na">android:paddingTop=</span><span class="s">"50dp"</span> <span class="nt">></span>
<span class="nt"><TextView</span>
<span class="na">android:layout_width=</span><span class="s">"wrap_content"</span>
<span class="na">android:layout_height=</span><span class="s">"wrap_content"</span>
<span class="na">android:layout_weight=</span><span class="s">"1"</span>
<span class="na">android:background=</span><span class="s">"#FFFF00"</span>
<span class="na">android:text=</span><span class="s">"1"</span> <span class="nt">/></span>
<span class="nt"><TextView</span>
<span class="na">android:layout_width=</span><span class="s">"wrap_content"</span>
<span class="na">android:layout_height=</span><span class="s">"wrap_content"</span>
<span class="na">android:layout_weight=</span><span class="s">"2"</span>
<span class="na">android:background=</span><span class="s">"#FF0000"</span>
<span class="na">android:text=</span><span class="s">"2"</span> <span class="nt">/></span>
<span class="nt"><TextView</span>
<span class="na">android:layout_width=</span><span class="s">"wrap_content"</span>
<span class="na">android:layout_height=</span><span class="s">"wrap_content"</span>
<span class="na">android:layout_weight=</span><span class="s">"3"</span>
<span class="na">android:background=</span><span class="s">"#000000"</span>
<span class="na">android:text=</span><span class="s">"3"</span> <span class="nt">/></span>
<span class="nt"></LinearLayout></span>
</pre></div>
<p>布局效果图1如下:</p>
<p><img alt="布局效果1" src="/images/layout_weight1.png"></p>
<p>此处各子组件<code>android:layout_weight</code>之和为6,TextView1、TextView2、TextView3各占剩余空间比例为1/6、2/6、3/6。各子组件所设置<code>android:layout_width</code>属性为<code>wrap_content</code>,即依内容而定,此处为各自文本字符串1、2、3的宽度;父组件宽度属性为<code>android:layout_width="match_parent"</code>,即撑满整个屏幕宽度。</p>
<blockquote>
<p>剩余空间 = 屏幕宽度 - 字符串1宽度 - 字符串2宽度 - 字符串3宽度</p>
<p>子组件1所占空间 = 字符串1宽度 + 1/6 * 剩余空间 </p>
<p>子组件2所占空间 = 字符串2宽度 + 2/6 * 剩余空间</p>
<p>字组件3所占空间 = 字符串3宽度 + 3/6 * 剩余空间</p>
</blockquote>
<p>因字符串1、2、3宽度比较小,粗略估计为0。那么此处各子组件所占组件比例则近似于1:2:3,符合效果图1。</p>
<h3>设置参数<code>android:layout_width:"match_parent"</code></h3>
<p>同样布局文件xml如下,和上文xml文件唯一不同的地方在于各子组件所设置宽度属性改为了<code>match_parent</code>:</p>
<div class="highlight"><pre><span></span><span class="cp"><?xml version="1.0" encoding="utf-8"?></span>
<span class="nt"><LinearLayout</span> <span class="na">xmlns:android=</span><span class="s">"http://schemas.android.com/apk/res/android"</span>
<span class="na">android:layout_width=</span><span class="s">"match_parent"</span>
<span class="na">android:layout_height=</span><span class="s">"match_parent"</span>
<span class="na">android:orientation=</span><span class="s">"horizontal"</span>
<span class="na">android:paddingTop=</span><span class="s">"50dp"</span> <span class="nt">></span>
<span class="nt"><TextView</span>
<span class="na">android:layout_width=</span><span class="s">"match_parent"</span>
<span class="na">android:layout_height=</span><span class="s">"wrap_content"</span>
<span class="na">android:layout_weight=</span><span class="s">"1"</span>
<span class="na">android:background=</span><span class="s">"#FFFF00"</span>
<span class="na">android:text=</span><span class="s">"1"</span> <span class="nt">/></span>
<span class="nt"><TextView</span>
<span class="na">android:layout_width=</span><span class="s">"match_parent"</span>
<span class="na">android:layout_height=</span><span class="s">"wrap_content"</span>
<span class="na">android:layout_weight=</span><span class="s">"2"</span>
<span class="na">android:background=</span><span class="s">"#FF0000"</span>
<span class="na">android:text=</span><span class="s">"2"</span> <span class="nt">/></span>
<span class="nt"><TextView</span>
<span class="na">android:layout_width=</span><span class="s">"match_parent"</span>
<span class="na">android:layout_height=</span><span class="s">"wrap_content"</span>
<span class="na">android:layout_weight=</span><span class="s">"3"</span>
<span class="na">android:background=</span><span class="s">"#000000"</span>
<span class="na">android:text=</span><span class="s">"3"</span> <span class="nt">/></span>
<span class="nt"></LinearLayout></span>
</pre></div>
<p>效果图2如下:</p>
<p><img alt="布局效果2" src="/images/layout_weight2.png"></p>
<p>看到图片你可能非常惊讶,怎么只显示1、2,而3没显示出来。不着急,根据计算公式,你就会知道结果。</p>
<p>同样此处各子组件<code>android:layout_weight</code>之和为6,TextView1、TextView2、TextView3各占剩余空间比例为1/6、2/6、3/6。各子组件和父组件宽度属性都为<code>android:layout_width="match_parent"</code>,即都撑满整个屏幕宽度。</p>
<blockquote>
<p>剩余空间 = 屏幕宽度 - TextView1所设置宽度(<strong>即屏幕宽度</strong>) - TextView2所设置宽度(即屏幕宽度) - TextView3所设置宽度(即屏幕宽度) = -2个屏幕宽度(<code>对的,没错。此处剩余空间为负</code>)</p>
<p>那么组件1所占空间 = TextView1所设置宽度(<strong>即屏幕宽度</strong>) + 1/6 * (-2个屏幕宽度) = 2/3个屏幕宽度</p>
<p>组件2所占空间 = TextView2所设置宽度(即屏幕宽度) + 2/6 * (-2个屏幕宽度) = 1/3个屏幕宽度</p>
<p>组件3所占空间 = TextView3所设置宽度(即屏幕宽度) + 3/6 * (-2个屏幕宽度) = 0</p>
</blockquote>
<p>根据计算,我们可以看到组件3所占空间为0,且组件1和组件2所占空间比为2:1,和效果图一样。</p>
<p>那么如果是属性<code>wrap_content</code>和<code>match_parent</code>混合使用的话,同样可应用公式算出,本文不在详述。</p>Linux学习笔记——有效群组(effective group)与初始群组(initial group)2013-12-18T16:09:00+08:002013-12-18T16:09:00+08:00xymelontag:www.xycoding.com,2013-12-18:/articles/2013/12/18/linux-group/<p><strong>有效群组(effective group)与初始群组(initial group)</strong></p>
<p>初始群组:/etc/passwd中第四栏所标志的GID;当用户登入系统,立刻拥有该群组的相关权限。</p>
<p>有效群组:当以某个用户身份登入后,输入<code>groups</code>命令第一出现的群组即为有效群组。它的作用是当用户建立新文件或目录时,所属群组为有效群组。</p>
<p><strong>有效群组(effective group)与初始群组(initial group)</strong></p>
<p>初始群组:/etc/passwd中第四栏所标志的GID;当用户登入系统,立刻拥有该群组的相关权限。</p>
<p>有效群组:当以某个用户身份登入后,输入<code>groups</code>命令第一出现的群组即为有效群组。它的作用是当用户建立新文件或目录时,所属群组为有效群组。</p>
<p><strong>/etc/group档案结构</strong></p>
<div class="highlight"><pre><span></span><span class="n">root</span><span class="o">:</span><span class="n">x</span><span class="o">:</span><span class="mi">0</span><span class="o">:</span><span class="n">root</span>
</pre></div>
<p>每一行代表一个群组,用<code>:</code>作为字段分隔符,共分为4部分。</p>
<blockquote>
<ol>
<li>组名</li>
<li>群组密码</li>
<li>GID</li>
<li>该群组所支持的帐号名称。例,若cow帐号想加入root群组,即为<code>root:x:0:root,cow</code></li>
</ol>
</blockquote>
<p><strong>切换有效群组</strong></p>
<p>使用命令<code>newgrp</code>切换,不过所切换的群组必须为该用户所加入的群组。<code>newgrp</code>是以另一个shell来提供群组切换功能的,如果想回到原本的环境中,可输入<code>exit</code>命令。</p>Linux学习笔记——'Pattern Space' and 'Hold Space' in sed2013-12-02T20:01:00+08:002013-12-02T20:01:00+08:00xymelontag:www.xycoding.com,2013-12-02:/articles/2013/12/02/linux-sed-space/<p>来源:<a href="http://stackoverflow.com/questions/12833714/the-concept-of-hold-space-and-pattern-space-in-sed">http://stackoverflow.com/questions/12833714/the-concept-of-hold-space-and-pattern-space-in-sed</a></p>
<p>When sed reads a file line by line, the line that has been currently read is inserted into the pattern buffer (pattern space). Pattern buffer is like the temporary buffer, the scratchpad where the current information is stored. When you tell sed to print, it prints the pattern buffer.</p>
<p>Hold buffer / hold space is like a long-term storage, such that you can catch something, store it and reuse it later when sed is processing another line. You do not directly process the hold space, instead, you need to copy it or append to the pattern space if you want to do something with it. For example, the print command p prints the pattern space only. Likewise, s operates on the pattern space.</p>
<p>来源:<a href="http://stackoverflow.com/questions/12833714/the-concept-of-hold-space-and-pattern-space-in-sed">http://stackoverflow.com/questions/12833714/the-concept-of-hold-space-and-pattern-space-in-sed</a></p>
<p>When sed reads a file line by line, the line that has been currently read is inserted into the pattern buffer (pattern space). Pattern buffer is like the temporary buffer, the scratchpad where the current information is stored. When you tell sed to print, it prints the pattern buffer.</p>
<p>Hold buffer / hold space is like a long-term storage, such that you can catch something, store it and reuse it later when sed is processing another line. You do not directly process the hold space, instead, you need to copy it or append to the pattern space if you want to do something with it. For example, the print command p prints the pattern space only. Likewise, s operates on the pattern space.</p>
<p>Here is an example:</p>
<div class="highlight"><pre><span></span>sed -n '1!G;h;$p'
</pre></div>
<p>(the <code>-n</code> option suppresses automatic printing of lines)</p>
<p>There are three commands here: <code>1!G</code>, <code>h</code> and <code>$p</code>. <code>1!G</code> has an address, 1 (first line), but the <code>!</code> means that the command will be executed everywhere but on the first line. <code>$p</code> on the other hand will only be executed on the last line. So what happens is this:</p>
<blockquote>
<ol>
<li>first line is read and inserted automatically into the pattern space</li>
<li>on the first line, first command is not executed; <code>h</code> copies the first line into the hold space.</li>
<li>now the second line replaces whatever was in the pattern space</li>
<li>on the second line, first we execute <code>G</code>, appending the contents of the hold buffer to the pattern buffer, separating it by a newline. The pattern space now contains the second line, a newline, and the first line.</li>
<li>Then, <code>h</code> command inserts the concatenated contents of the pattern buffer into the hold space, which now holds the reversed lines two and one.</li>
<li>We proceed to line number three -- go to the point (3) above.</li>
</ol>
</blockquote>
<p>Finally, after the last line has been read and the hold space (containing all the previous lines in a reverse order) have been appended to the pattern space, pattern space is printed with <code>p</code>. As you have guessed, the above does exactly what the tac command does -- prints the file in reverse.</p>Linux学习笔记——权限管理(2)2013-11-23T23:10:00+08:002013-11-23T23:10:00+08:00xymelontag:www.xycoding.com,2013-11-23:/articles/2013/11/23/linux-authority2/<p><strong>特殊文件权限:SUID,SGID,SBIT</strong></p>
<p><strong>SUID</strong>:当s这个标志出现在<strong>文件拥有者</strong>权限的x权限上时,如【-rwsr-xr-x】,此时被称为Set UID,简称为SUID的特殊权限,且只对文件有效。</p>
<ol>
<li>SUID权限仅对二进制程序(binary program)有效;</li>
<li>执行者对于该程序需要具有x的可执行权限;</li>
<li>本权限尽在执行该程序的过程中有效(run-time);</li>
<li>执行者将具有该拥有者(owner)的权限。</li>
</ol>
<p><strong>特殊文件权限:SUID,SGID,SBIT</strong></p>
<p><strong>SUID</strong>:当s这个标志出现在<strong>文件拥有者</strong>权限的x权限上时,如【-rwsr-xr-x】,此时被称为Set UID,简称为SUID的特殊权限,且只对文件有效。</p>
<ol>
<li>SUID权限仅对二进制程序(binary program)有效;</li>
<li>执行者对于该程序需要具有x的可执行权限;</li>
<li>本权限尽在执行该程序的过程中有效(run-time);</li>
<li>执行者将具有该拥有者(owner)的权限。</li>
</ol>
<p>简单的说就是具有x权限的执行者(groups/others)在执行过程中会具有拥有者(owner)的权限。</p>
<p><strong>SGID</strong>:当s这个标志出现在<strong>群组权限</strong>的x权限上时,如【-rwx--s--x】,此时被称为Set GID,简称为SGID的特殊权限,可对文件和目录有效。对目录设定SGID权限后,具有如下功能:</p>
<ol>
<li>用户对于此目录具有r与x的权限时,该用户能够进入此目录;</li>
<li>用户在此目录下的有效群组(effective group)将会变成该目录的群组;</li>
<li>用途:若用户在此目录下具有w的权限(可新建文件),则使用者所建立的新文件的群组与此目录的群组相同。</li>
</ol>
<p><strong>SBIT(Sticky Bit)</strong>:当t这个标志出现在<strong>其他权限</strong>的x权限上时,如【drwxrwxrwt】,只对目录有效,对于目录作用如下:</p>
<ol>
<li>当用户对于此目录具有w,x的权限,亦即具有写入的权限时;</li>
<li>当用户在该目录下建立文件和目录时,仅有自己与root才有权力删除该文件。</li>
</ol>Linux学习笔记——权限管理(1)2013-11-22T20:01:00+08:002013-11-22T20:01:00+08:00xymelontag:www.xycoding.com,2013-11-22:/articles/2013/11/22/linux-authority1/<p><strong>权限对于文件</strong></p>
<ol>
<li>r(read):可读,如读取文本内容</li>
<li>w(write):可编辑、新增、修改该文件的内容(只针对内容,不能删除文件)</li>
<li>x(execute):可被系统执行</li>
</ol>
<p><strong>权限对于文件</strong></p>
<ol>
<li>r(read):可读,如读取文本内容</li>
<li>w(write):可编辑、新增、修改该文件的内容(只针对内容,不能删除文件)</li>
<li>x(execute):可被系统执行</li>
</ol>
<p><strong>权限对于目录</strong></p>
<ol>
<li>r(read contents in directory):可查询该目录下包含哪些文件</li>
<li>w(modify contents of directory):可对该目录下的子目录和文件进行增、删、更名、移位</li>
<li>x(access directory):可进入该目录</li>
</ol>
<p><strong>经典范例</strong></p>
<p>假设有个帐号名为cow,它对目录/home/cow/具有rwx权限。若在此目录下有个test的文件,该文件权限如下:</p>
<div class="highlight"><pre><span></span>rwx------ 1 root root 4365 Sep 19 23:20 test
</pre></div>
<p>请问cow对此文件权限为什么?可否删除此文件?</p>
<p>解答:</p>
<blockquote>
<p>如题,cow对此文件来说是[others]身份,因此对于该文件它没有任何权限。但是该文件位于其home目录下,它对于home目录具有rwx完整权限,因此对于test这个文件来说,它是可以删除的。</p>
</blockquote>
<p><strong>默认权限</strong></p>
<ul>
<li>文件:-rw-rw-rw-(666)</li>
<li>目录:drwxrwxrwx(777)</li>
</ul>
<p><em>umask指令数字显示时代表【该默认值需要减掉的权限】</em></p>
<blockquote>
<p>例:umask数值为022</p>
<p>建立文件时默认权限:(-rw-rw-rw-)-(-----w--w-)= (-rw-r--r--)</p>
<p>建立目录时默认权限:(drwxrwxrwx)(d----w--w-) = (drwxr-xr-x)</p>
</blockquote>使用Pelican和GitHub Pages搭建个人博客 —— 进阶篇2013-11-22T08:00:00+08:002013-11-27T12:11:00+08:00xymelontag:www.xycoding.com,2013-11-22:/articles/2013/11/22/blog-advance/<h3>独立域名</h3>
<ol>
<li>在<a href="http://www.godaddy.com/">godaddy</a>上购买域名,推荐使用国内优惠码(搜索一下,你就知道~),价格会非常便宜,比如我花了111.95元就购买了两年期限的域名,你说爽不爽。</li>
<li>购买成功后,需要将DNS服务解析转回到国内,推荐使用<a href="https://www.dnspod.cn/">DNSPod</a>,至于为啥需要转回国内,你猜呢?DNSPod官网上有详细介绍,请点击<a href="https://support.dnspod.cn/Kb/showarticle/?qtype=%E5%8A%9F%E8%83%BD%E4%BB%8B%E7%BB%8D%E5%8F%8A%E4%BD%BF%E7%94%A8%E6%95%99%E7%A8%8B&tsid=42">Godaddy注册商域名修改DNS地址</a>。</li>
<li>完成第二步后,需注册<a href="https://www.dnspod.cn/">DNSPod</a>,请点击<a href="https://support.dnspod.cn/Kb/showarticle/tsid/177/">学会使用DNSPod,仅需三步</a>,按照教程添加上刚购买的域名后,为让其指向github分配给我们的二级域名,只需在DNSPod中添加A记录,指向204.232.175.78,请参见github pages教程<a href="https://help.github.com/articles/setting-up-a-custom-domain-with-pages">Setting up a custom domain with Pages</a>,如有不懂之处,也可看看DNSPod的帮助中心,里面的介绍非常详细。</li>
<li>最后一步,登录github,进入我们所创建的cowfighting.github.io库中,在根目录创建一个名为CNAME的文件,内容为在godaddy购买的域名,例www.xycoding.com,到此为止,即大功告成。
<h3>独立域名</h3>
<ol>
<li>在<a href="http://www.godaddy.com/">godaddy</a>上购买域名,推荐使用国内优惠码(搜索一下,你就知道~),价格会非常便宜,比如我花了111.95元就购买了两年期限的域名,你说爽不爽。</li>
<li>购买成功后,需要将DNS服务解析转回到国内,推荐使用<a href="https://www.dnspod.cn/">DNSPod</a>,至于为啥需要转回国内,你猜呢?DNSPod官网上有详细介绍,请点击<a href="https://support.dnspod.cn/Kb/showarticle/?qtype=%E5%8A%9F%E8%83%BD%E4%BB%8B%E7%BB%8D%E5%8F%8A%E4%BD%BF%E7%94%A8%E6%95%99%E7%A8%8B&tsid=42">Godaddy注册商域名修改DNS地址</a>。</li>
<li>完成第二步后,需注册<a href="https://www.dnspod.cn/">DNSPod</a>,请点击<a href="https://support.dnspod.cn/Kb/showarticle/tsid/177/">学会使用DNSPod,仅需三步</a>,按照教程添加上刚购买的域名后,为让其指向github分配给我们的二级域名,只需在DNSPod中添加A记录,指向204.232.175.78,请参见github pages教程<a href="https://help.github.com/articles/setting-up-a-custom-domain-with-pages">Setting up a custom domain with Pages</a>,如有不懂之处,也可看看DNSPod的帮助中心,里面的介绍非常详细。</li>
<li>最后一步,登录github,进入我们所创建的cowfighting.github.io库中,在根目录创建一个名为CNAME的文件,内容为在godaddy购买的域名,例www.xycoding.com,到此为止,即大功告成。
</li>
</ol>
<h3>URL配置</h3>
<p>一个好的URL地址不光搜索引擎友好,也让自己看得舒心,pelican中也完全考虑到这一点,只需简单的配置就可打造出漂亮的URL。</p>
<p>打开<code>pelicanconf.py</code>配置文件,按照如下修改或添加,具体配置请参见<a href="http://docs.getpelican.com/en/3.3.0/settings.html">官方文档</a>。</p>
<div class="highlight"><pre><span></span>ARTICLE_URL = 'pages/{date:%Y}/{date:%m}/{date:%d}/{slug}/'
ARTICLE_SAVE_AS = 'pages/{date:%Y}/{date:%m}/{date:%d}/{slug}/index.html'
</pre></div>
<h3>pelican插件</h3>
<p>插件有很多种,比如带来便利性、观赏性、易搜索性等,大家可根据需要进行选择。本博客中选用了<code>sitemap</code>,<code>summary</code>,<code>neighbors</code>等插件。首先,和主题一样,选择一个文件夹,去<a href="https://github.com/getpelican/pelican-plugins">pelican插件开源库</a>clone下来插件,选择自己喜欢的~所有插件的功能都可在开源库中找到答案。</p>
<div class="highlight"><pre><span></span>//clone插件
git clone https://github.com/getpelican/pelican-plugins
</pre></div>
<p>打开<code>pelicanconf.py</code>配置文件,按照如下修改或添加,插件的使用方法请进入相应的插件开源库进行了解。</p>
<div class="highlight"><pre><span></span>PLUGIN_PATH = 'pelican-plugins' //设置路径
PLUGINS = ['summary','sitemap','neighbors'] //选用插件
SITEMAP = {
'format': 'xml',
'priorities': {
'articles': 0.7,
'indexes': 0.5,
'pages': 0.5
},
'changefreqs': {
'articles': 'monthly',
'indexes': 'daily',
'pages': 'monthly'
}
}
</pre></div>
<h3>Google Analytics和Google Webmasters</h3>
<p>注册<a href="http://www.google.com/analytics/">Google Analytics</a>和<a href="http://www.google.com/webmasters/">Google Webmasters</a>可以更好的管理自己的站点,更好的让google收录等。具体教程非常简单,注册完基本就能知道~最后记得在<code>pelicanconf.py</code>中进行配置。</p>
<div class="highlight"><pre><span></span>GOOGLE_ANALYTICS = 'Tracking ID'
</pre></div>
<h3>最后</h3>
<p>博客到此为此基本介绍完毕,以后想起什么再补充吧。其实我对现在这个主题还有不满意的地方,比如<code>key words,description</code>等没有地方设置,这对搜索引擎不友好;还有左边栏没有热门文章等~接下来再慢慢完善。</p>
<hr>
<p>博客源码地址:<a href="https://github.com/xymelon/xymelon.github.io">https://github.com/xymelon/xymelon.github.io</a></p>
<p>博客主题地址:<a href="https://github.com/xymelon/xycoding-gum">https://github.com/xymelon/xycoding-gum</a></p>使用Pelican和GitHub Pages搭建个人博客 —— 基础篇2013-11-21T11:21:00+08:002013-11-27T12:11:00+08:00xymelontag:www.xycoding.com,2013-11-21:/articles/2013/11/21/blog-create/<h3>前言</h3>
<p>一直以来都希望拥有属于自己的个人博客,随性发点信息,写点技术感想,记录自己的生活,重要的是不受广告的影响、不被河蟹、不会担心有一天被莫名其妙地消失。作为一名技术渣硕,抛弃拿来主义,勇于摸索,踏出第一步,不再纸上谈兵,从现在开始。</p>
<p>本博客是在windows下搭建完成。不过这和linux下是类似的,因为搭建过程是在git bash中进行,git bash的命令风格是仿linux的。
<h3>前言</h3>
<p>一直以来都希望拥有属于自己的个人博客,随性发点信息,写点技术感想,记录自己的生活,重要的是不受广告的影响、不被河蟹、不会担心有一天被莫名其妙地消失。作为一名技术渣硕,抛弃拿来主义,勇于摸索,踏出第一步,不再纸上谈兵,从现在开始。</p>
<p>本博客是在windows下搭建完成。不过这和linux下是类似的,因为搭建过程是在git bash中进行,git bash的命令风格是仿linux的。
</p>
<h3>知识储备</h3>
<p>搭建博客的工具选用了基于python的pelican,相比wordpress等其它工具来说,它比较轻盈并有很多令人兴奋的<a href="http://docs.getpelican.com/en/3.3.0/#features">特性</a>,再配合免费无限制的github pages,近乎完美。搭建过程中涉及如下技术知识,不过你不必害怕,只是使用它们的开源框架而已,并不需要自己编码,点击可以了解它们是如何的强大,当然你也可以略过它们,后面遇到时再进行了解。<br/>
假如你不能打开它们,原因你懂的,请爬墙解决~</p>
<blockquote>
<p><a href="https://github.com/">github</a></p>
<p><a href="http://pages.github.com/">github pages</a></p>
<p><a href="http://git-scm.com/">git</a></p>
<p><a href="http://www.python.org/">python</a></p>
<p><a href="https://pypi.python.org/pypi/pip">pip</a></p>
<p><a href="http://blog.getpelican.com/">pelican</a></p>
<p><a href="http://daringfireball.net/projects/markdown/syntax">markdown</a></p>
<p><a href="http://markdownpad.com/">markdownpad</a></p>
</blockquote>
<h3>下载安装</h3>
<p>请点击下载以下内容,本文会一步一步介绍如何安装,不着急,慢慢来。</p>
<blockquote>
<p><a href="http://www.python.org/getit/">python3下载</a></p>
<p><a href="http://msysgit.github.io/">git下载</a> </p>
<p><a href="http://markdownpad.com/">markdownpad下载</a></p>
<p><a href="http://www.equation.com/servlet/equation.cmd?fa=make">window下make下载</a> </p>
</blockquote>
<ol>
<li>安装python,这个就不用多说了吧~</li>
<li>安装git,简单。git bash使用教程请参见<a href="http://www.git-scm.com/book/zh">官方文档</a>~ </li>
<li>将python安装文件夹中scripts和make.exe加入环境变量。</li>
<li>安装pip(PS:Python 2 >=2.7.9 或 Python 3 >=3.4安装python时已包含),具体可以看<a href="http://www.pip-installer.org/en/latest/installing.html">官网介绍</a>,或者<a href="http://tech.crandom.com/2011/10/21/python3_pip.html">Windows环境在python3装pip</a>。</li>
<li>安装pelican和markdown,至于为什么使用markdown语法,这就看个人喜好呢,下载的markdownpad就是window下markdown的编辑器呢,非常简单,linux等其它操作系统编辑器请自行google。你也可以选用REST语法,具体请参见<a href="http://daringfireball.net/projects/markdown/syntax">markdown教程</a>和<a href="https://beinggeekbook.readthedocs.org/en/latest/rst.html">REST教程</a>。<div class="highlight"><pre><span></span>pip install pelican
pip install markdown
</pre></div>
</li>
</ol>
<h3>主体搭建</h3>
<p>打开git bash,进入一个自己喜欢的文件夹(注意文件夹名最好不为空,因为后续make html命令会出错),执行以下命令</p>
<div class="highlight"><pre><span></span>mkdir blog //创建文件夹,名称可根据自己喜欢定
cd blog
pelican-quickstart
</pre></div>
<p>pelican-quickstart执行命令后,会提示输入博客的配置项,除了少数几个必填以外,其它都可以选择默认,而且都可以在<code>pelicanconf.py</code>文件中进行更改,所以你可以随意选择,如下图~</p>
<p><img alt="pelican-quickstart" src="/images/pelican-quickstart.png"></p>
<p>命令成功执行后,会出现pelican的框架,如下所示</p>
<div class="highlight"><pre><span></span>blog/
├── content # 存放输入的markdown或RST源文件
│ └── (pages) # 存放手工创建的静态页面,可选
├── output # 存放最终生成的静态博客
├── develop_server.sh # 测试服务器
├── Makefile # 管理博客的Makefile
├── pelicanconf.py # 配置文件
└── publishconf.py # 发布文件,可删除
</pre></div>
<h3>书写博文</h3>
<p>完成上述博客主体搭建后,使用markdownpad创建一个<code>.md</code>文件,保存于<code>content</code>文件夹中。博文格式如下所示</p>
<p><img alt="markdown" src="/images/markdown.png"></p>
<p>图中左侧是我们书写的markdown格式的源文件,右边则是即使预览效果,很棒吧~
至于源文件顶部<code>Title,Date,Category等</code>内容则是必须的,具体可参见<a href="http://docs.getpelican.com/en/3.3.0/getting_started.html#writing-content-using-pelican">文档</a>,它们各自意义如下</p>
<div class="highlight"><pre><span></span><span class="n">Title</span><span class="o">:</span> <span class="err">文章标题</span>
<span class="n">Date</span><span class="o">:</span> <span class="err">创建日期</span>
<span class="n">Modified</span><span class="o">:</span> <span class="err">修改日期</span>
<span class="n">Category</span><span class="o">:</span> <span class="err">文章分类,标志本文处于该分类下</span>
<span class="n">Tags</span><span class="o">:</span> <span class="err">文章标签,标志本文处于该标签下</span>
<span class="n">Slug</span><span class="o">:</span> <span class="n">URL中该文章的链接地址</span>
<span class="n">Author</span><span class="o">:</span> <span class="err">作者</span>
</pre></div>
<p>写完后,回到<code>blog</code>目录下,执行<code>make html</code>命令进行博客的生成</p>
<div class="highlight"><pre><span></span>make html
(pelican e:/blog/content/ -o e:/blog/output -s e:/blog/pelicanconfg.py)
</pre></div>
<p><code>make html</code>命令将把刚才写的博文生成html,存放到output目录下,如果你没有make命令,也可执行第二行的pelican命令。接着执行<code>make serve</code>开启测试服务器</p>
<div class="highlight"><pre><span></span>make serve
(cd e:/blog/output/ && python -m pelican.server)
</pre></div>
<p><code>make serve</code>命令也可由第二行替代,在浏览器中输入<code>http://localhost:8000</code>即可看到博文效果。</p>
<h3>主题选择</h3>
<p>回到<code>blog</code>目录下,按如下步骤下载pelican官方主题,从里面挑选出自己喜欢的主题吧,大多数主题预览界面你可以打开这个<a href="http://pelicanthemes.com/">网页</a>进行查看。不过如今pelican又新出了很多主题,所以你需看看<a href="https://github.com/getpelican/pelican-themes">pelican主题开源库</a>。</p>
<div class="highlight"><pre><span></span>git clone https://github.com/getpelican/pelican-themes.git
</pre></div>
<p>打开<code>pelicanconf.py</code>配置文件,更改或添加<code>THEME</code>为自己喜欢的主题,例如本博客所挑选的gum,更多的配置含义请关注<a href="http://docs.getpelican.com/en/3.3.0/settings.html">官方文档</a>。</p>
<div class="highlight"><pre><span></span>THEME = 'pelican-themes/gum'
</pre></div>
<h3>添加评论系统</h3>
<p>开启个人博客的原因在于分享知识,分享就需要交流,评论模块当然少不了。在<a href="https://disqus.com/">Disqus</a>上申请帐号,按照流程Disqus会分配给你站点的Shortname,记牢Shortname,如果忘了请进入admin/settings中查看。然后同理,在<code>pelicanconf.py</code>添加</p>
<div class="highlight"><pre><span></span>DISQUS_SITENAME = Shortname
</pre></div>
<h3>博文发布</h3>
<p>经过以上的折腾,离成功只差最后一步呢。写好的博文总要找个站点发布吧,本文基于github pages,当然你也可以选择你熟悉的站点服务器。具体步骤如<a href="https://pages.github.com/">官方教程</a>,非常简单。按照官方教程,你就拥有一个二级域名和一个<a href="https://github.com/xymelon/xymelon.github.io">版本库</a>,比如我的<a href="http://xymelon.github.io/">http://xymelon.github.io/</a>,当然现在访问会出现跳转,这在后面独立域名章节会介绍。</p>
<p>进入上一章节所示的<code>output</code>目录下,依次执行以下命令</p>
<div class="highlight"><pre><span></span>git init
git add .
git remote add origin https://github.com/xymelon/xymelon.github.io
git pull origin master
git commit -m 'first blog'
git push origin master
</pre></div>
<p>熟悉git的同学肯定知道上述命令表达的是什么意思吧,如果你还不甚了解,没关系,先照做或者看看git的<a href="http://www.git-scm.com/book/zh">官方文档</a>。上述命令很长,如果你比较熟悉了,可以修改Makefile文件进行一键上传~</p>
<p>最后打开浏览器输入github pages的二级域名,效果如下</p>
<p><img alt="article" src="/images/article.png"></p>
<p>目前为止,还算比较成功。从上图可以看出,书写源文件的<code>Title,Date,Category,Slug等</code>内容每个都有其的用武之处,具体你可以慢慢了解。</p>
<h3>TODO</h3>
<p>目前来说,博客的基本使用已经可以了,不过我们依然可以对其进行完善。接下来的一篇<a href="http://www.xycoding.com/articles/2013/11/22/blog-advance/">文章</a>我会介绍以下几个方面</p>
<ol>
<li>独立域名</li>
<li>博文URL格式配置,例<a href="http://www.xycoding.com/pages/2013/11/21/create-blog/">http://www.xycoding.com/pages/2013/11/21/create-blog/</a></li>
<li>pelican插件的使用(Sitemap等)</li>
<li>Google Analytics和Google Webmasters</li>
</ol>
<h3>感想</h3>
<p>这是我第一次比较认真的写教程,参考了网上很多的例子,不过都觉得他们讲得不够详细,让我花了很多时间去看官方介绍,当然这可能是我太渣的缘故,看不懂大神们的教程,哈哈。不过从中我也学到了很多心得,比如不懂的地方第一选择是官方文档,本文基本给出了搭建博客过程的所有链接。</p>
<p>最后,不管怎样,属于自己个人的博客已经搭建起来,从现在开始,记录自己的生活吧~</p>
<hr>
<p>博客源码地址:<a href="https://github.com/xymelon/xymelon.github.io">https://github.com/xymelon/xymelon.github.io</a></p>
<p>博客主题地址:<a href="https://github.com/xymelon/xycoding-gum">https://github.com/xymelon/xycoding-gum</a></p>读研究生三个月有感2013-11-20T08:25:00+08:002013-11-20T08:25:00+08:00xymelontag:www.xycoding.com,2013-11-20:/articles/2013/11/20/life-graduate/<h3>初进校园</h3>
<p>北邮给人的感觉就是很小,还没进入学校之前,就听byr说过,如果你要带你朋友逛北邮校园,那么请带他来到主楼前,面朝西门。那么在你后面就是东门,左手边就是南门,前面就是西门,右手边就是北门~马上就介绍完毕,哈哈。不过麻雀虽小,五脏俱全~
<h3>初进校园</h3>
<p>北邮给人的感觉就是很小,还没进入学校之前,就听byr说过,如果你要带你朋友逛北邮校园,那么请带他来到主楼前,面朝西门。那么在你后面就是东门,左手边就是南门,前面就是西门,右手边就是北门~马上就介绍完毕,哈哈。不过麻雀虽小,五脏俱全~
</p>
<h3>信息黄埔</h3>
<p>是的,北邮号称信息黄埔,这个称号还算名副其实。学校里基本上一大半的学院都和IT有关,耳濡目染,码农风气传遍校园。</p>
<p>逛逛北邮人论坛,看看校招实习信息,你会发现在IT界面里byr遍地开花,IT公司大部分都在北邮开宣讲会,三大运营商不说了,每年北京主场都在北邮进行召开。今年微软的唯一北京宣讲会居然也选在了北邮,其他的诸如阿里、百度等互联网公司那是肯定来的。而且北邮师兄师姐在IT公司里也有很多很多,每天都有各种内推实习等,对byr很是照顾。</p>
<h3>研究生生活</h3>
<p>研究生生活基本都靠自己呢,毕竟都是好大的人呢,周围的人都很勤奋,在寝室里基本看不到身影,每个人都在为自己的目标而努力着。我的研究生方向不是那么的喜欢,看看论文,查查资料,更多的是和理论打交道,虽然说这本该是研究生该干的事,不过私以为还是喜欢有实践的感觉,不管怎样,毕业是要发论文的。</p>
<p>唯一让我郁闷的是我这导师从大实验室分出来单独给他的学生一个房间,这样就和大部队有点脱节,真不爽。不过这有什么办法啊,只有顺其自然呢,哎!!!</p>
<p>实验室不忙,每天都有属于自己的时间,我也还有很多书还没看,总感觉研究生时间不够,要学的太多,真为自己所荒废的本科而后悔。我也有自己的目标,正在为之而努力奋斗着,希望有很好的收获吧。</p>