安卓寫的多了,見過的那些比較好的控件都想用Flutter寫出來,前一陣模仿了個登陸按鈕,就在昨天完善了一番,感覺可以拿出手了,聽說沒圖都沒興趣看的!那還是先上圖吧(注意:錄製的GIF看起來比較卡,實際上絲滑流暢)
用戶只需要關注顯示異常信息即可。登陸成功則直接跳轉(因爲登陸成功沒有動畫)、
老規矩,先看看我寫的控件怎麼使用,後面再講源碼哈
第一步:添加以下代碼到你的pubspec.yaml文件
dependencies:
animatedloginbutton: "^0.1.1"
第二步:導包,添加以下代碼到你要使用的文件下
import 'package:animatedloginbutton/animatedloginbutton.dart';
第三步:寫你的業務代碼(下面是我寫的示例代碼)
import 'dart:io';
import 'dart:convert';
import 'package:flutter/material.dart';
import 'package:animatedloginbutton/animatedloginbutton.dart';
class LoginAnimationDemo extends StatefulWidget{
@override
State<StatefulWidget> createState() {
return new LoginAnimationDemoState();
}
}
class LoginAnimationDemoState extends State<LoginAnimationDemo>{
//使用系統的請求
var httpClient = new HttpClient();
var url = "https://github.com/";
var _result="";
final LoginErrorMessageController loginErrorMessageController=LoginErrorMessageController();
@override
void initState() {
super.initState();
}
@override
Widget build(BuildContext context) {
return new Scaffold(
appBar: new AppBar(
title: new Text("登陸按鈕動畫"),
),
body: new Center(
child:new Container(
child:new AnimatedLoginButton(
loginErrorMessageController:loginErrorMessageController,
onTap: () async {
try {
var request = await httpClient.getUrl(Uri.parse(url));
var response = await request.close();
if (response.statusCode == HttpStatus.ok) {
_result = await response.transform(utf8.decoder).join();
//拿到數據後,對數據進行梳理
loginErrorMessageController.showErrorMessage("網絡異常");
} else {
_result = 'ERROR CODE: ${response.statusCode}';
loginErrorMessageController.showErrorMessage("網絡異常 $_result");
}
} catch (exception) {
_result = '網絡異常';
loginErrorMessageController.showErrorMessage("網絡異常");
}
print(_result);
},
),
),
),
);
}
}
使用就需要三步,使用還是很簡單的,經過測試,性能也挺好,再附上參數使用表就跟完美了,一起來看看吧
loginTip | 登陸按鈕提示 |
width | button的寬度 |
height | button的高度 |
indicatorStarRadian | 弧形指標器的起始角度(旋轉的那個帶箭頭的東西) |
indicatorWidth | 指標器的寬度 |
indicatorColor | 指標器的顏色 |
buttonColorNormal | 未登陸時Button的顏色 |
buttonColorError | 登陸異常Button的顏色 |
textStyleNormal | 未登錄時Button文字的樣式 |
textStyleError | 登陸異常時Button文字的樣式 |
onTap | 點擊事件,在此方法中執行登陸操作 |
loginErrorMessageController | 用來顯示異常信息 |
showErrorTime | 異常信息顯示時間 |
源碼:
到了將源碼的環節了,我們帶着問題去想怎麼實現吧! 假如我們用自帶的button,他也能收縮(改變寬度),收縮完成後要用層佈局套一個Iamge去旋轉,後面再伸長,這些都可以做到,但是,後面的異常狀態恢復到登陸狀態的動畫怎麼做?想來想去還是得畫出button來
說畫就畫,肯定得先畫出button得樣子吧,感覺就是兩個圓中間夾了一個矩形,中間寫上文字而已,想讓button變短,那我將中間矩形的寬度通過動畫減小不就行了,想想還是蠻有道理的,於是我就寫出瞭如下的代碼
///畫圓角的button draw corner Button
void drawCornerButton(Canvas canvas,Paint paint,double halfHeight,String txt,TextStyle txtStyle){
canvas.drawCircle(new Offset(halfHeight, halfHeight), halfHeight, paint);
double rectRight = width - halfHeight;
canvas.drawRect(new Rect.fromLTRB(halfHeight, 0.0, rectRight, height), paint);
canvas.drawCircle(new Offset(rectRight, halfHeight), halfHeight, paint);
TextPainter textPainter = new TextPainter();
textPainter.textDirection = TextDirection.ltr;
textPainter.text = new TextSpan(text: txt, style: txtStyle);
textPainter.layout();
double textStarPositionX = (width - textPainter.size.width) / 2;
double textStarPositionY = (height - textPainter.size.height) / 2;
textPainter.paint(canvas, new Offset(textStarPositionX, textStarPositionY));
}
↓↓↓這Button的收縮是沒問題了,但是那個旋轉的指標器怎麼弄呀,這指標器就是個圓裏面畫個彎彎的箭頭麼,畫圓誰還不會了,這箭頭最最簡單的方法就是你找個圖片畫上去,然後開啓動畫,將圖片旋轉就行了;但是我這人能畫就畫,絕對不用圖片,一個Path能搞定的事,就不用麻煩美工了,而且我這個還是自適應的屏幕的;閒話少說,畫好Pth也要旋轉,不過是旋轉CustomPaint 控件,看怎麼利用path畫出效果
void drawCircleButton(Canvas canvas,Paint paint,double halfHeight){
canvas.drawCircle(new Offset(halfHeight, halfHeight), halfHeight, paint);
paint.color=indicatorColor;
paint.style=PaintingStyle.stroke;
paint.strokeWidth=indicatorWidth;
double smallCircleRadius=halfHeight/2;
canvas.drawArc(new Rect.fromLTRB(smallCircleRadius, smallCircleRadius, height-smallCircleRadius, height-smallCircleRadius), indicatorStarRadian, indicatorRadian, false, paint);
double radian=indicatorStarRadian+indicatorRadian;
Path path=getPath(smallCircleRadius,radian);
canvas.save();
canvas.translate(halfHeight, halfHeight);
canvas.drawPath(path, paint);
canvas.restore();
}
///畫等腰三角形
Path getPath(double radius,double radian){
Path path=new Path();
double yPoint=sin(radian)*radius;
double xPoint=cos(radian)*radius;
double halfSide=isoscelesTriangle/2;
path.moveTo(xPoint, yPoint+halfSide);
path.lineTo(xPoint, yPoint-halfSide);
double xVertex=xPoint+sqrt(pow(isoscelesTriangle, 2)-pow(halfSide,2));
path.lineTo(xVertex, yPoint);
path.close();
return path;
}
↓↓↓現在能伸縮,能旋轉,就缺最後一步了,就是從異常狀態恢復到登陸狀態的動畫,這個動畫實在異常狀態的Button上再繪製一層,而這一層是由2個Layer疊加出來的,疊加是利用Flutter的BlendMode和canvas.saveLayer完成的,和原生安卓的一樣哈,疊加出來的這一層顯示多大的面積,是由動畫來控制的,看代碼
paint.color=recoverButtonColor;
Paint layerPaint=Paint();
canvas.saveLayer(new Rect.fromLTWH(0.0, 0.0, recoverCircleRadius, size.height), layerPaint);
drawCornerButton(canvas,paint,halfHeight,recoverText,recoverTextStyle);
layerPaint.blendMode=BlendMode.dstIn;
canvas.saveLayer(new Rect.fromLTWH(0.0, 0.0, recoverCircleRadius, size.height), layerPaint);
canvas.drawCircle(new Offset(0.0, 0.0), recoverCircleRadius, paint);
canvas.restore();
canvas.restore();
↓↓↓該畫的畫完了,該疊加的也疊加了,但是我們還得控制什麼時候畫圓角的Button,什麼時候畫圓形旋轉的Button,什麼時候繪製疊加層,整個邏輯在paint方法裏面通過一些狀態的判斷完成,看下paint方法吧
@override
void paint(Canvas canvas, Size size) {
Paint paint=new Paint();
paint.color=buttonColorShow;
double halfHeight = height / 2;
if(height<width) {
drawCornerButton(canvas,paint,halfHeight,showText,showTextStyle);
}else{
drawCircleButton(canvas,paint,halfHeight);
}
if(isNeedRecoverLoginState){
paint.color=recoverButtonColor;
Paint layerPaint=Paint();
canvas.saveLayer(new Rect.fromLTWH(0.0, 0.0, recoverCircleRadius, size.height), layerPaint);
drawCornerButton(canvas,paint,halfHeight,recoverText,recoverTextStyle);
layerPaint.blendMode=BlendMode.dstIn;
canvas.saveLayer(new Rect.fromLTWH(0.0, 0.0, recoverCircleRadius, size.height), layerPaint);
canvas.drawCircle(new Offset(0.0, 0.0), recoverCircleRadius, paint);
canvas.restore();
canvas.restore();
}
}
↓↓↓繪製的類全寫完了,我們只只要在build裏面引用,傳好參數就就行,不過我們的控件還得寫個點擊方法,(用戶寫的話話不好控制,我們寫好點擊讓用戶寫回調更好),看下build方法
@override
Widget build(BuildContext context) {
return new GestureDetector(
child: new RotationTransition( //佈局中加載時動畫的weight
child: new CustomPaint(
size: new Size(widgetWidth, widgetHeight),
painter: new LoginPainter(
width:widgetWidth,
height:widgetHeight,
recoverText:widget.loginTip,
indicatorStarRadian: widget.indicatorStarRadian,
indicatorWidth:widget.indicatorWidth,
buttonColorShow:_buttonColor,
indicatorColor:widget.indicatorColor,
recoverTextStyle: widget.textStyleNormal,
recoverButtonColor: widget.buttonColorNormal,
isNeedRecoverLoginState: isNeedRecoverLoginState,
recoverCircleRadius: _recoverCircleRadius,
showText: _loginTextTip,
showTextStyle: _textStyle,
),
),
turns: new Tween(begin: 0.0, end: 150.0).animate(
animationControllerWait)
..addStatusListener((animationStatus) {
if (animationStatus == AnimationStatus.completed) {
animationControllerWait.repeat();
}
}),
),
onTap: (){
if(!_isInAnimation) {
_isInAnimation=true;
controllerStart.forward();
}
},
);
}
剩下的就是一些動畫初始化,和動畫的監聽,動畫和動畫之間邏輯的協調,這些都很簡單的,寫一個,剩下的複製就行,不過複製的時候要小心,我就是在複製的時候變量名沒改完,結果動畫變得很詭異,注意:dispose的時候,要註銷AnimationController,dispose的時候,要註銷AnimationController,dispose的時候,要註銷AnimationController;看一下Start動畫變量的初始化和監聽
void initAnimationStart(){
animationStart = new Tween(begin: widgetWidth, end: widgetHeight).animate(controllerStart)
..addListener(() {
if(isReset){
return;
}
if(mounted){
setState(() {
widgetWidth=animationStart.value;
});
}
});
animationStart.addStatusListener((animationStatus){
if(animationStatus==AnimationStatus.completed){
//動畫結束時首先將animationController重置
isReset=true;
controllerStart.reset();
isReset=false;
animationControllerWait.forward();
handleAnimationEnd();
}else if(animationStatus==AnimationStatus.forward){
}
});
}
到這裏我們的代碼就完了,使用起來簡單,源碼稍微有那麼一點點長,要是有不理解的地方,你可以聯繫我,如果在使用的過程中有什麼問題,也請留言,隨時爲你解答,
最後附上源碼地址:https://github.com/OpenFlutter/PullToRefresh;
裏面有很多更酷的控件,歡迎Star;如果喜歡Flutter,可以加入我們哦,我們的QQ羣是 :892398530