如何使用Flutter實(shí)現(xiàn)58同城中的加載動(dòng)畫詳解
前言
在應(yīng)用中執(zhí)行耗時(shí)操作時(shí),為了避免界面長(zhǎng)時(shí)間等待造成假死的現(xiàn)象,往往會(huì)添加一個(gè)加載中的動(dòng)畫來(lái)提醒用戶,在58同城中也不例外,而且我們并沒有使用系統(tǒng)默認(rèn)的加載動(dòng)畫,而是制作了一個(gè)具有58特色的加載動(dòng)畫。
在本篇文章中,給大家分享下筆者使用Flutter實(shí)現(xiàn)58同城中加載動(dòng)畫的過(guò)程。先看一下加載動(dòng)畫的效果:
動(dòng)畫效果乍看比較復(fù)雜,難以看出端倪,其實(shí)我們可以先調(diào)慢動(dòng)畫的速度,這樣能夠比較清晰地分析出動(dòng)畫的流程。
動(dòng)畫的流程
動(dòng)畫由兩個(gè)圓弧的動(dòng)效組成,兩個(gè)圓弧的起始點(diǎn)角度和掃過(guò)的弧度隨著時(shí)間規(guī)律變化。仔細(xì)觀察會(huì)發(fā)現(xiàn),兩個(gè)圓弧的動(dòng)效其實(shí)是一樣的,只不過(guò)起始位置是不一樣的。我們先看下外部大圓弧的運(yùn)動(dòng)規(guī)律。
大圓弧從x軸正方向開始運(yùn)動(dòng),按照動(dòng)畫的運(yùn)動(dòng)規(guī)律,可以將動(dòng)畫分為三個(gè)階段:
第一階段:圓弧起點(diǎn)的在x軸正方向,終點(diǎn)的角度x軸正方向開始向下逐漸增大,直到終點(diǎn)到達(dá)y軸負(fù)方向位置,最終圓弧掃過(guò)的角度為180度。
第二階段:圓弧掃過(guò)的角度保持在180度,起點(diǎn)和終點(diǎn)一起順時(shí)針旋轉(zhuǎn),直到旋轉(zhuǎn)180度后終點(diǎn)到達(dá)x軸正方向。
第三階段:圓弧的終點(diǎn)保持在x軸正方向,起點(diǎn)順時(shí)針旋轉(zhuǎn),直到起點(diǎn)也到達(dá)x軸正方向,此時(shí)完成一個(gè)完整的動(dòng)畫。接下來(lái)繼續(xù)重復(fù)動(dòng)畫的第一階段,組成一個(gè)連貫的動(dòng)畫。
分析完動(dòng)畫的流程,思路就很清晰了,我們按照動(dòng)畫流程把動(dòng)畫拆分成三部分,通過(guò)對(duì)圓弧的起點(diǎn)、終點(diǎn)和掃過(guò)角度的變換,組合成一個(gè)完整的動(dòng)畫,然后不斷地重復(fù),最后就變成了一個(gè)加載中的動(dòng)畫效果。
接下來(lái)開始寫代碼實(shí)現(xiàn)。
由于動(dòng)畫是由一個(gè)圓弧不斷變化組成的,如果使用Android,我們很自然的想到可以使用Canvas來(lái)進(jìn)行圓弧的繪制,然后根據(jù)時(shí)間的變化不停地重新繪制圓弧,從而實(shí)現(xiàn)動(dòng)畫效果。那么在Flutter中是否也存在Canvas呢,答案是肯定的,F(xiàn)lutter和Android一樣,也存在Canvas。
Flutter中的Canvas
Flutter中使用 CustomPainter 類在Canvas上進(jìn)行繪制,該類包含一個(gè) paint() 方法,該方法提供了一個(gè)Canvas對(duì)象,可以用來(lái)繪制各種圖形。
abstract class CustomPainter extends Listenable {
void paint(Canvas canvas, Size size);
}
不過(guò)在Flutter中一切皆是Widget,而承載Canvas功能的Widget是 CustomPaint 類。 CustomPaint 包含一個(gè)painter屬性,用來(lái)指定進(jìn)行繪制的 CustomPainter,源碼如下:
class CustomPaint extends SingleChildRenderObjectWidget {
const CustomPaint({
Key key,
this.painter,
});
final CustomPainter painter;
}
Flutter中的Canvas和Android類似,提供了一系列的API用來(lái)繪制點(diǎn)、線、圓形、正方形等,而且API很類似,對(duì)比一下Flutter與Android中Canvas的常見API(具體的參數(shù)列表請(qǐng)參考文檔和源碼,篇幅有限不再一一列出):
| Android | Flutter | |
|---|---|---|
| 點(diǎn) |
drawPoint() drawPoints() |
drawPoints() |
| 線 |
drawLine() drawLines() |
drawLine() |
| 圓 | drawCircle() | drawCircle() |
| 橢圓 | drawOval() | drawOval() |
| 圓弧 | drawArc() | drawArc() |
| 矩形 | drawRect() | drawRect() |
| Path | drawPath() | drawPath() |
| 圖片 | drawBitmap() | drawImage() |
| 文字 | drawText() | drawParagraph() |
| 變換 |
save() restore() |
save() restore() |
要繪制動(dòng)畫中的圓弧,應(yīng)該使用 drawArc() 方法來(lái)實(shí)現(xiàn),這里需要注意的是drawArc()方法的參數(shù):startAngle和sweepAngle的單位是弧度(180度等于π弧度)。
具體來(lái)看一下 Canvas.drawArc() 方法的參數(shù)列表:
/// rect: 圓弧四周范圍所形成的矩形,在本篇中圓弧為圓形,可以使用Rect.fromCircle()確定圓弧的范圍 /// startAngle: 圓弧起始點(diǎn)的角度,x軸正方向?yàn)?度,按順時(shí)針遞增,y軸負(fù)方向?yàn)?0度,以此類推 /// sweepAngle: 圓弧掃過(guò)的角度,即圓弧終點(diǎn)所在的角度為startAngle + sweepAngle /// useCenter: 如果為true,圓弧兩端會(huì)與圓心相連,形成一個(gè)扇形,本篇中應(yīng)為false /// paint: 畫筆,下文中會(huì)進(jìn)行簡(jiǎn)單介紹 void drawArc(Rect rect, double startAngle, double sweepAngle, bool useCenter, Paint paint)
在Canvas的一系列方法中會(huì)發(fā)現(xiàn)一個(gè)熟悉的名稱:Paint,與Android類似,F(xiàn)lutter中的Paint類也是用來(lái)描述畫筆的。
Paint類
Paint類位于 dart.ui 庫(kù)中,Paint類保存了畫筆的顏色、粗細(xì)、是否抗鋸齒、著色器等屬性。
下面簡(jiǎn)單的介紹下幾個(gè)常用的屬性:
Paint paint = Paint() ..color = Color(0xFFFF552E) ..strokeWidth = 2.0 ..style = PaintingStyle.stroke ..isAntiAlias = true ..shader = LinearGradient(colors: []).createShader(rect) ..strokeCap = StrokeCap.round ..strokeJoin = StrokeJoin.bevel;
屬性說(shuō)明:
- color:Color類型,設(shè)置畫筆的顏色。
- strokeWidth:double類型,設(shè)置畫筆的粗細(xì)。
- style:PaintingStyle枚舉類型,設(shè)置畫筆的樣式, PaintingStyle.stroke 為描邊, PaintingStyle.fill 為填充。
- isAntiAlias:bool類型,設(shè)置是否抗鋸齒,true為開啟抗鋸齒。
- shader:Shader類型,著色器,一般用來(lái)繪制漸變效果,可以使用 LinearGradient、 RadialGradient、 SweepGradient 等。
- strokeCap:StrokeCap枚舉類型,設(shè)置線條兩端點(diǎn)的樣式, StrokeCap.butt 為無(wú)(默認(rèn)值), StrokeCap.round 為圓形, StrokeCap.square 為方形。
- strokeJoin:StrokeJoin枚舉類型,設(shè)置線條交匯處的樣式, StrokeJoin.miter 為銳角, StrokeJoin.round 為圓弧, StrokeJoin.bevel 為斜角,可以參考下圖方便理解:
熟悉了Canvas和Paint的使用之后,就能夠繪制出加載動(dòng)畫的圓弧了。當(dāng)然,只是繪制出圓弧并沒有什么用,主要是怎么讓圓弧動(dòng)起來(lái)。
Flutter中的動(dòng)畫
想要讓圓弧動(dòng)起來(lái),我們需要使用到Flutter的動(dòng)畫。下面先來(lái)介紹下Flutter中動(dòng)畫的實(shí)現(xiàn)。
Flutter中的動(dòng)畫相關(guān)的類主要有以下幾個(gè):
Animation:動(dòng)畫的核心類,是一個(gè)抽象類。用來(lái)生成動(dòng)畫執(zhí)行過(guò)程中的插值,輸出的結(jié)果可以是線性或曲線的,Animation對(duì)象與UI渲染沒有任何關(guān)系。
abstract class Animation<T> extends Listenable implements ValueListenable<T> {
/// 添加動(dòng)畫狀態(tài)的監(jiān)聽
void addStatusListener(AnimationStatusListener listener);
/// 移除動(dòng)畫狀態(tài)的監(jiān)聽
void removeStatusListener(AnimationStatusListener listener);
/// 獲取當(dāng)前動(dòng)畫的狀態(tài)
AnimationStatus get status;
/// 獲取當(dāng)前動(dòng)畫的插值,執(zhí)行動(dòng)畫時(shí)需要根據(jù)該值進(jìn)行UI繪制等
T get value;
}
AnimationController:動(dòng)畫的管理類,繼承自 Animation<double>。默認(rèn)情況下在給定的時(shí)間范圍內(nèi)線性生成從0.0到1.0的值。
AnimationController對(duì)象需要傳遞一個(gè)vsync參數(shù),它接收一個(gè)TickerProvider類型的對(duì)象,主要職責(zé)是創(chuàng)建Ticker。Flutter應(yīng)用在啟動(dòng)時(shí)會(huì)綁定一個(gè)SchedulerBinding,可以給每一次屏幕刷新添加回調(diào),Ticker就是通過(guò)SchedulerBinding來(lái)添加屏幕刷新的回調(diào),當(dāng)屏幕刷新時(shí),會(huì)通知到綁定的Ticker回調(diào)。假如動(dòng)畫的UI不在當(dāng)前屏幕,比如鎖屏?xí)r,鎖屏后屏幕停止刷新,不會(huì)通知SchedulerBinding,Ticker也就不會(huì)觸發(fā),這樣就能夠防止屏幕外的動(dòng)畫消耗不必要的資源。
class AnimationController extends Animation<double>
with AnimationEagerListenerMixin, AnimationLocalListenersMixin, AnimationLocalStatusListenersMixin {
/// value:動(dòng)畫的初始值,默認(rèn)是lowerBound
/// duration:動(dòng)畫執(zhí)行的時(shí)長(zhǎng)
/// lowerBound:動(dòng)畫的最小值,默認(rèn)值為0.0
/// upperBound:動(dòng)畫的最大值,默認(rèn)值為1.0
/// vsync:可以通過(guò) `with SingleTickerProviderStateMixin` 傳入StatefulWidget對(duì)象
AnimationController({
double value,
this.duration,
this.lowerBound = 0.0,
this.upperBound = 1.0,
@required TickerProvider vsync,
}) {
_ticker = vsync.createTicker(_tick);
}
Ticker _ticker;
/// Ticker的回調(diào),每次屏幕刷新都會(huì)回調(diào)
void _tick(Duration elapsed) {
notifyListeners();
}
/// 開始播放動(dòng)畫
TickerFuture forward({ double from })
/// 反向播放動(dòng)畫
TickerFuture reverse({ double from })
/// 設(shè)置動(dòng)畫重復(fù)執(zhí)行
TickerFuture repeat({ double min, double max, bool reverse = false, Duration period })
/// 釋放動(dòng)畫資源
void dispose()
}
CurvedAnimation:非線性動(dòng)畫類,繼承自 Animation<double>。CurvedAnimation可以使用curve屬性指定曲線函數(shù)Curve,類似Android動(dòng)畫的插值器,F(xiàn)lutter中已經(jīng)實(shí)現(xiàn)了許多常用的曲線,在Curves類中可以找到,比如Curves.linear、Curves.decelerate、Curves.ease。也可以繼承Curve類重寫 transform() 方法來(lái)實(shí)現(xiàn)自定義的曲線函數(shù)。
class CurvedAnimation extends Animation<double>
with AnimationWithParentMixin<double> {
/// parent:指定AnimationController對(duì)象
/// curve:指定動(dòng)畫的曲線函數(shù)
CurvedAnimation({
@required this.parent,
@required this.curve,
})
}
abstract class Curve {
/// 計(jì)算動(dòng)畫執(zhí)行中`t`點(diǎn)的插值,可以自定義曲線函數(shù)
double transform(double t)
}
Tween:補(bǔ)間值的生成類,繼承自 Animatable<T>。
由于AnimationController的值范圍默認(rèn)為0.0到1.0,如果需要不同的范圍或數(shù)據(jù)類型,可以使用Tween指定動(dòng)畫值的范圍。Tween不僅能返回double類型的值,還有IntTween、ColorTween、SizeTween等各種返回不同數(shù)據(jù)類型的子類。
使用Tween對(duì)象需要調(diào)用 animate() 方法,傳入AnimationController對(duì)象,該方法會(huì)返回一個(gè)Animation,這樣就可以獲取到動(dòng)畫的插值了。
class Tween<T extends dynamic> extends Animatable<T> {
/// begin:動(dòng)畫的起始值
/// end:動(dòng)畫的結(jié)束值
Tween({ this.begin, this.end });
/// 可以把double類型的動(dòng)畫插值轉(zhuǎn)換成任何類型的值
T transform(double t)
/// parent:傳入AnimationController對(duì)象
/// 返回Animation對(duì)象,使用Animation.value獲取動(dòng)畫當(dāng)前的插值
Animation<T> animate(Animation<double> parent)
}
AnimatedBuilder:用于構(gòu)建動(dòng)畫的Widget,將動(dòng)畫和要執(zhí)行動(dòng)畫的Widget關(guān)聯(lián)起來(lái),繼承關(guān)系為AnimatedBuilder → AnimatedWidget → StatefulWidget。
class AnimatedBuilder extends AnimatedWidget {
const AnimatedBuilder({
@required Listenable animation,
@required this.builder,
});
/// typedef TransitionBuilder = Widget Function(BuildContext context, Widget child);
/// builder是一個(gè)函數(shù),返回Widget對(duì)象
final TransitionBuilder builder;
@override
Widget build(BuildContext context) {
return builder(context, child);
}
}
abstract class AnimatedWidget extends StatefulWidget {
const AnimatedWidget({
@required this.listenable,
});
@protected
Widget build(BuildContext context);
@override
_AnimatedState createState() => _AnimatedState();
}
class _AnimatedState extends State<AnimatedWidget> {
@override
void initState() {
super.initState();
widget.listenable.addListener(_handleChange);
}
@override
void dispose() {
widget.listenable.removeListener(_handleChange);
super.dispose();
}
void _handleChange() {
setState(() { });
}
@override
Widget build(BuildContext context) => widget.build(context);
}
分析上面列出的源碼,AnimatedWidget是一個(gè)StatefulWidget。當(dāng)AnimatedWidget關(guān)聯(lián)的_AnimatedState初始化時(shí),會(huì)注冊(cè)動(dòng)畫的監(jiān)聽函數(shù)_handleChange,_handleChange監(jiān)聽函數(shù)中又調(diào)用了setState()方法,即動(dòng)畫插值每次改變時(shí)都會(huì)調(diào)用build()方法。_AnimatedState.build()方法中又調(diào)用了AnimatedWidget.build()方法,在AnimatedBuilder中實(shí)現(xiàn)了AnimatedWidget.build()方法:調(diào)用屬性builder生成Widget,最終實(shí)現(xiàn)了動(dòng)畫與Widget的綁定。
加載動(dòng)畫的實(shí)現(xiàn)
了解了Flutter的動(dòng)畫后,再結(jié)合之前對(duì)加載動(dòng)畫流程的分析,加載動(dòng)畫可分成三個(gè)階段,我們可以依賴Tween類,指定值的范圍從0.0到3.0變化,當(dāng)然也可以只使用AnimationController,指定lowerBound和upperBound的值分別為0.0和3.0。這里之所以不使用CurvedAnimation,是因?yàn)榧虞d動(dòng)畫的圓弧是線性變化的,不存在加速減速,沒有必要使用。
大圓弧能夠?qū)崿F(xiàn)了,我們?cè)賮?lái)看內(nèi)部的小圓弧,仔細(xì)觀察會(huì)發(fā)現(xiàn)小圓弧的變化規(guī)律與大圓弧完全一致,只不過(guò)小圓弧的起始位置在x軸負(fù)方向,與大圓弧正好相差180度,也就是π弧度。在繪制大圓弧的同時(shí),可以很輕松的計(jì)算出小圓弧的起點(diǎn)的角度(即大圓弧起點(diǎn)的角度+π弧度)。
至此整個(gè)動(dòng)畫的實(shí)現(xiàn)思路就清晰了:
- 自定義加載動(dòng)畫的Widget,繼承自CustomPaint類。
- 使用AnimationController、Tween創(chuàng)建動(dòng)畫,動(dòng)畫的值范圍從0.0到3.0線性變化,并且設(shè)置動(dòng)畫重復(fù)執(zhí)行。動(dòng)畫插值每遞增1.0代表動(dòng)畫執(zhí)行的一個(gè)階段。
- 繼承CustomPainter類,實(shí)現(xiàn)paint()方法繪制圓弧。根據(jù)動(dòng)畫的插值判斷當(dāng)前屬于動(dòng)畫的哪個(gè)階段,再計(jì)算出圓弧的起點(diǎn)、掃過(guò)的角度,繪制出兩個(gè)圓弧。
下面是實(shí)現(xiàn)加載動(dòng)畫的關(guān)鍵代碼:
import 'dart:math';
import 'package:flutter/material.dart';
class WubaLoadingWidget extends StatefulWidget {
@override
_WubaLoadingWidgetState createState() => _WubaLoadingWidgetState();
}
class _WubaLoadingWidgetState extends State<WubaLoadingWidget>
with SingleTickerProviderStateMixin {
AnimationController _animationController;
Animation<double> _animation;
@override
void initState() {
super.initState();
_animationController = new AnimationController(
// 可以指定lowerBound、upperBound,使用AnimationController對(duì)象
// lowerBound: 0.0,
// upperBound: 3.0,
vsync: this,
duration: const Duration(milliseconds: 1500),
);
_animation = Tween(begin: 0.0, end: 3.0)
.animate(_animationController);
_animationController.forward(); // 執(zhí)行動(dòng)畫
_animationController.repeat(); // 設(shè)置動(dòng)畫循環(huán)執(zhí)行
}
@override
void dispose() {
// 調(diào)用dispose()方法釋放動(dòng)畫資源
_animationController.dispose();
super.dispose();
}
@override
Widget build(BuildContext context) {
return AnimatedBuilder(
animation: _animationController,
builder: (BuildContext context, Widget child) {
return Container(
child: CustomPaint(
painter: _LoadingPaint(
value: _animation.value,
),
),
);
},
);
}
}
class _LoadingPaint extends CustomPainter {
final double value;
final Paint _outerPaint; // 大圓弧的Paint
final Paint _innerPaint; // 小圓弧的Paint
_LoadingPaint({
this.value,
});
@override
void paint(Canvas canvas, Size size) {
double startAngle = 0;
double sweepAngle = 0;
// 動(dòng)畫的第一階段:圓弧起點(diǎn)為0度,終點(diǎn)的角度遞增
if (value <= 1.0) {
startAngle = 0;
sweepAngle = value * pi;
}
// 動(dòng)畫的第二階段:圓弧掃過(guò)的弧度為π弧度(180度),起點(diǎn)、終點(diǎn)一起順時(shí)針旋轉(zhuǎn),一共旋轉(zhuǎn)π弧度
else if (value <= 2.0) {
startAngle = (value - 1) * pi;
sweepAngle = pi;
}
// 動(dòng)畫的第三階段:圓弧的終點(diǎn)不變,起點(diǎn)從x軸負(fù)方向開始順時(shí)針旋轉(zhuǎn),直到起點(diǎn)也到達(dá)x軸正方向
else {
startAngle = pi + (value - 2) * pi;
sweepAngle = (3 - value) * pi;
}
// 繪制外圈的大圓弧
canvas.drawArc(outerRect, startAngle, sweepAngle, false, _outerPaint);
// 繪制內(nèi)圈的小圓弧
canvas.drawArc(innerRect, startAngle + pi, sweepAngle, false, _innerPaint);
}
@override
bool shouldRepaint(CustomPainter oldDelegate) {
return true;
}
}
總結(jié)
Flutter的Canvas、Paint與Android的API非常類似,基本的思路也一致,對(duì)于Android同學(xué)比較容易掌握。
Flutter中動(dòng)畫的實(shí)現(xiàn)相較于Android邏輯更加清晰簡(jiǎn)單,方便易用。AnimatedBuilder類巧妙的將UI與動(dòng)畫整合在一起,把UI和動(dòng)畫職責(zé)分離,這種思路值得學(xué)習(xí)。Flutter中的動(dòng)畫還有路由過(guò)渡動(dòng)畫、Hero動(dòng)畫、切換動(dòng)畫組件AnimatedSwitcher等,有需要的同學(xué)可以查找相關(guān)資料。
如果大家需要定制一些個(gè)性化的加載動(dòng)畫,推薦一個(gè)GitHub的開源項(xiàng)目:flutter_spinkit,這個(gè)插件提供了很多種常用的加載動(dòng)畫效果。
好了,以上就是這篇文章的全部?jī)?nèi)容了,希望本文的內(nèi)容對(duì)大家的學(xué)習(xí)或者工作具有一定的參考學(xué)習(xí)價(jià)值,謝謝大家對(duì)我們的支持。
上一篇:詳解Android 8.1.0 Service 中 彈出 Dialog的方法
欄 目:Android
下一篇:Android實(shí)現(xiàn)傾斜角標(biāo)樣式
本文標(biāo)題:如何使用Flutter實(shí)現(xiàn)58同城中的加載動(dòng)畫詳解
本文地址:http://www.jygsgssxh.com/a1/Android/9146.html
您可能感興趣的文章
- 01-10如何給Flutter界面切換實(shí)現(xiàn)點(diǎn)特效
- 01-10使用RecyclerView實(shí)現(xiàn)水平列表
- 01-10Android 使用URLConnection下載音頻文件的方法
- 01-10Android使用MediaPlayer和TextureView實(shí)現(xiàn)視頻無(wú)縫切換
- 01-10Flutter適配深色模式的方法(DarkMode)
- 01-10Android使用MediaCodec將攝像頭采集的視頻編碼為h264
- 01-10Flutter 滾動(dòng)監(jiān)聽及實(shí)戰(zhàn)appBar滾動(dòng)漸變的實(shí)現(xiàn)
- 01-10Flutter里面錯(cuò)誤捕獲的正確方法
- 01-10Android中butterknife的使用與自動(dòng)化查找組件插件詳解
- 01-10android 使用okhttp可能引發(fā)OOM的一個(gè)點(diǎn)


閱讀排行
- 1C語(yǔ)言 while語(yǔ)句的用法詳解
- 2java 實(shí)現(xiàn)簡(jiǎn)單圣誕樹的示例代碼(圣誕
- 3利用C語(yǔ)言實(shí)現(xiàn)“百馬百擔(dān)”問(wèn)題方法
- 4C語(yǔ)言中計(jì)算正弦的相關(guān)函數(shù)總結(jié)
- 5c語(yǔ)言計(jì)算三角形面積代碼
- 6什么是 WSH(腳本宿主)的詳細(xì)解釋
- 7C++ 中隨機(jī)函數(shù)random函數(shù)的使用方法
- 8正則表達(dá)式匹配各種特殊字符
- 9C語(yǔ)言十進(jìn)制轉(zhuǎn)二進(jìn)制代碼實(shí)例
- 10C語(yǔ)言查找數(shù)組里數(shù)字重復(fù)次數(shù)的方法
本欄相關(guān)
- 01-10Android自定義View之繪制圓形頭像功能
- 01-10Android實(shí)現(xiàn)雙擊返回鍵退出應(yīng)用實(shí)現(xiàn)方
- 01-10android實(shí)現(xiàn)簡(jiǎn)單計(jì)算器功能
- 01-10android實(shí)現(xiàn)記住用戶名和密碼以及自動(dòng)
- 01-10C++自定義API函數(shù)實(shí)現(xiàn)大數(shù)相乘算法
- 01-10Android 友盟第三方登錄與分享的實(shí)現(xiàn)代
- 01-10android實(shí)現(xiàn)指紋識(shí)別功能
- 01-10如何給Flutter界面切換實(shí)現(xiàn)點(diǎn)特效
- 01-10Android實(shí)現(xiàn)圓形漸變加載進(jìn)度條
- 01-10Emoji表情在Android JNI中的兼容性問(wèn)題詳
隨機(jī)閱讀
- 08-05DEDE織夢(mèng)data目錄下的sessions文件夾有什
- 01-10SublimeText編譯C開發(fā)環(huán)境設(shè)置
- 01-11ajax實(shí)現(xiàn)頁(yè)面的局部加載
- 08-05dedecms(織夢(mèng))副欄目數(shù)量限制代碼修改
- 04-02jquery與jsp,用jquery
- 01-11Mac OSX 打開原生自帶讀寫NTFS功能(圖文
- 01-10delphi制作wav文件的方法
- 01-10C#中split用法實(shí)例總結(jié)
- 01-10使用C語(yǔ)言求解撲克牌的順子及n個(gè)骰子
- 08-05織夢(mèng)dedecms什么時(shí)候用欄目交叉功能?


