发送邮件用什么控件?能不能直接填写邮箱地址、邮件标题和内容就可以调用控件方法发送出去的,在任意机子上?

ooolinux 2019-09-16 12:39:00
发送邮件用什么控件?能不能直接填写邮箱地址、邮件标题和内容就可以调用控件方法发送出去的,在任意机子上?
...全文
780 23 打赏 收藏 转发到动态 举报
写回复
用AI写文章
23 条回复
切换为时间正序
请发表友善的回复…
发表回复
善待流浪猫 2019-11-26
  • 打赏
  • 举报
回复
procedure TfrmContactUs.btnSendMailClick(Sender: TObject); begin if edtMailTo.Text <> '' then if (Pos('@', edtMailTo.Text) = 0) or (Pos('.', edtMailTo.Text) = 0) then begin ShowMessage('收件人邮箱格式错误'); Exit; end; if edtMailFrom.Text = '' then begin ShowMessage('发件人不能为空'); Exit; end; if edtMailSubject.Text = '' then begin ShowMessage('邮件主题不能为空'); Exit; end; oMsg.From.Address := oSMTP.Username; if edtMailTo.Text <> '' then oMsg.Recipients.EMailAddresses := edtMailTo.Text else oMsg.Recipients.EMailAddresses := oSMTP.Username; oMsg.Subject := edtMailSubject.Text; oMsg.Body.Text := mmoMailBody.Text + #13 + #13 + '发件人:' + edtMailFrom.Text; try if not oSMTP.Connected then oSMTP.Connect; oSMTP.Send(oMsg); ShowMessage('邮件发送成功'); except on E: Exception do ShowMessage('ERROR: ' + E.Message); end; end;
ooolinux 2019-11-26
  • 打赏
  • 举报
回复
你这个界面不错,怎么弄的? @善待流浪猫
ooolinux 2019-11-09
  • 打赏
  • 举报
回复
引用 20 楼 advance_coder 的回复:
[quote=引用 19 楼 ooolinux的回复:][quote=引用 18 楼 advance_coder 的回复:] 不知道你自己是否会写socket,会写的话,可以百度RFC(里面有多方面的协议标准文档,都是英文的),再在rfc网站搜索smtp有详细介绍。祝好运。
那不是造轮子吗[/quote] 这还是最简单的呢! smtp 还需要扩展协议,如加密的时候。 如果你只是简单发邮件那就找控件,或者搜些简易代码,要是以后会用到加密发送,那还是自己造吧。 网上搜些简易发送邮件代码,对照SMTP协议看一下,你会发现很简单的![/quote] 我现在用Indy组件可以发送了。
advance_coder 2019-11-09
  • 打赏
  • 举报
回复
引用 19 楼 ooolinux的回复:
[quote=引用 18 楼 advance_coder 的回复:] 不知道你自己是否会写socket,会写的话,可以百度RFC(里面有多方面的协议标准文档,都是英文的),再在rfc网站搜索smtp有详细介绍。祝好运。
那不是造轮子吗[/quote] 这还是最简单的呢! smtp 还需要扩展协议,如加密的时候。 如果你只是简单发邮件那就找控件,或者搜些简易代码,要是以后会用到加密发送,那还是自己造吧。 网上搜些简易发送邮件代码,对照SMTP协议看一下,你会发现很简单的!
ooolinux 2019-11-09
  • 打赏
  • 举报
回复
引用 18 楼 advance_coder 的回复:
不知道你自己是否会写socket,会写的话,可以百度RFC(里面有多方面的协议标准文档,都是英文的),再在rfc网站搜索smtp有详细介绍。祝好运。
那不是造轮子吗
advance_coder 2019-11-09
  • 打赏
  • 举报
回复
不知道你自己是否会写socket,会写的话,可以百度RFC(里面有多方面的协议标准文档,都是英文的),再在rfc网站搜索smtp有详细介绍。祝好运。
ooolinux 2019-11-03
  • 打赏
  • 举报
回复
IdMessage1->CharSet="GBK";
ooolinux 2019-11-03
  • 打赏
  • 举报
回复
用: IdMessage1->From->Address=IdSMTP1->Username; IdMessage1->Subject=Edit1->Text; // IdMessage1->Body->Assign(Memo1->Lines); IdMessage1->Body->Text=Memo1->Text; 邮件发送成功了,但是win10下收到邮件内容乱码,全是 ??????? ????? 而标题显示正常。不知道是啥问题?
ooolinux 2019-11-03
  • 打赏
  • 举报
回复
引用 14 楼 nikotin 的回复:
qq的邮箱,要在邮箱设置里面启用smtp服务才能发送邮件,163的也是类似。另外qq邮箱的smtp端口不是默认的,要设置成qq邮箱推荐的端口。你网上搜下看看。
163的邮箱,我启用了smtp,现在连接没问题了,发送部分出错:mail from must equal authorized user 我代码: IdMessage1->From->Name=IdSMTP1->Username; 不知道啥问题了?
nikotin 2019-11-02
  • 打赏
  • 举报
回复
qq的邮箱,要在邮箱设置里面启用smtp服务才能发送邮件,163的也是类似。另外qq邮箱的smtp端口不是默认的,要设置成qq邮箱推荐的端口。你网上搜下看看。
  • 打赏
  • 举报
回复
就是hello,握手命令,把自己的域名告诉接收者
ooolinux 2019-09-18
  • 打赏
  • 举报
回复
引用 12 楼 早打大打打核战争 的回复:
就是hello,握手命令,把自己的域名告诉接收者


好像还有一个EHLO命令的?
ooolinux 2019-09-17
  • 打赏
  • 举报
回复
引用 2 楼 老大i 的回复:
https://blog.csdn.net/zhouky1993/article/details/60953892
我连接 mail.163.com ,出现: Socket Error # 10061 Connection refused. 不知道什么原因?
ooolinux 2019-09-17
  • 打赏
  • 举报
回复
问个题外的,SMTP有关的HELO name是啥,英文缩写什么的?
ooolinux 2019-09-17
  • 打赏
  • 举报
回复
引用 9 楼 ouyongke 的回复:
[quote=引用 8 楼 ooolinux 的回复:]
[quote=引用 2 楼 老大i 的回复:]
https://blog.csdn.net/zhouky1993/article/details/60953892


我连接 mail.163.com ,出现:
Socket Error # 10061
Connection refused.
不知道什么原因?[/quote]
很多年不玩发邮件了,因为历史原因(当年最方便开发垃圾邮件工具的就是Delphi),所以现在无法用Delphi搞发邮件工具也正常,因为人家服务器就是禁止你Delphi过来连接。
我记得早年的时候,要修改id控件底层的一段代码,好像是邮件通讯握手协议什么的,让服务器不认为是IdSmtp控件过来连接,而且你的情况需要再测试一下,看具体是什么原因连不上163。
比如QQ邮箱和其他的邮箱都正常,就只是163连接不正常,那就是人家和协Delphi;如果都连不上,那就是配置有问题。[/quote]

我参考了链接里的代码,Host为mail.163.com或者mail.qq.com都连接不上,用户名和密码应该是对的,我去掉了try语句,让上层的Application消息循环自己捕捉异常,这样可以显示异常原因。
  • 打赏
  • 举报
回复
引用 8 楼 ooolinux 的回复:
[quote=引用 2 楼 老大i 的回复:]
https://blog.csdn.net/zhouky1993/article/details/60953892


我连接 mail.163.com ,出现:
Socket Error # 10061
Connection refused.
不知道什么原因?[/quote]
很多年不玩发邮件了,因为历史原因(当年最方便开发垃圾邮件工具的就是Delphi),所以现在无法用Delphi搞发邮件工具也正常,因为人家服务器就是禁止你Delphi过来连接。
我记得早年的时候,要修改id控件底层的一段代码,好像是邮件通讯握手协议什么的,让服务器不认为是IdSmtp控件过来连接,而且你的情况需要再测试一下,看具体是什么原因连不上163。
比如QQ邮箱和其他的邮箱都正常,就只是163连接不正常,那就是人家和协Delphi;如果都连不上,那就是配置有问题。
老大i 2019-09-16
  • 打赏
  • 举报
回复
引用 3 楼 ooolinux 的回复:

procedure TForm1.btnSendMailClick(Sender: TObject);
begin
//------------------------------------
//设置登录信息
//ZkyIdSMTP为控件名,
//可自己更改为与上面的一样的名字。
//格式为:
//<TIdSMIP控件名>.<属性>:=<自己的信息>
//------------------------------------
ZkyIdSMTP.AuthenticationType := atLogin;//设置登录类型,一般不变动
ZkyIdSMTP.Host := 'mail.qq.com'; //设置SMTP地址
ZkyIdSMTP.UserId := '1111@qq.com'; //设置登录账号
ZkyIdSMTP.Password := '1111'; //设置登录密码
ZkyIdSMTP.Port := 25; //设置端口,必须要整形型
try
ZkyIdSMTP.Connect; //连接服务器
except //如果连接失败,提示并退出程序
ShowMessage('连接服务器失败');
Exit;
end;
ShowMessage('成功连接服务器。'+#10#13+'点击确认后准备发邮件。');


//------------------------------------
//配置发送的内容,格式为:
//<TIdMessage的控件名> . <属性>:=
//------------------------------------
ZkyIdMessage.From.name := '1111@qq.com';//设置发件人姓名
ZkyIdmessage.Recipients.EMailAddresses:='2222@qq.com';//设置收件人邮箱地址(此为单发邮件的语句,群发时需去掉)
Zkyidmessage.Subject := '这是邮件的主题'; //设置邮件主题
Zkyidmessage.body.text := '这是邮件的内容';//设置邮件内容


//------------------------------------
//群发邮件:
//ZkyIdMessage.Recipients.add.Text:='<收件人邮箱>';
//ZkyIdMessage.BCCList.Add.Address := '<抄送人邮箱>' ;
//ZkyIdMessage.CCList.Add.Address := '<密送人邮箱>' ;
//发送给多人的时候,多打几遍语句。
//------------------------------------


try //发邮件
ZkyIdSMTP.Send(ZkyIdMessage);
Showmessage('邮件发送成功!');
except
Showmessage('邮件发送失败!');
end;


end;

————————————————
版权声明:本文为CSDN博主「楠珂伊梦千里月」的原创文章,遵循 CC 4.0 BY-SA 版权协议,转载请附上原文出处链接及本声明。
原文链接:https://blog.csdn.net/zhouky1993/article/details/60953892


[quote=引用 2 楼 老大i 的回复:]
https://blog.csdn.net/zhouky1993/article/details/60953892


必须把登录密码也写进去吗?不登录能发送吗?[/quote]
当然要的,用于登录,并且用这个号发邮件(没登录账号用什么发呢)
ooolinux 2019-09-16
  • 打赏
  • 举报
回复

procedure TForm1.btnSendMailClick(Sender: TObject);
begin
  //------------------------------------
  //设置登录信息
  //ZkyIdSMTP为控件名,
  //可自己更改为与上面的一样的名字。  
  //格式为:
  //<TIdSMIP控件名>.<属性>:=<自己的信息>  
  //------------------------------------
  ZkyIdSMTP.AuthenticationType := atLogin;//设置登录类型,一般不变动  
  ZkyIdSMTP.Host := 'mail.qq.com';   //设置SMTP地址
  ZkyIdSMTP.UserId := '1111@qq.com';   //设置登录账号
  ZkyIdSMTP.Password := '1111';      //设置登录密码
  ZkyIdSMTP.Port := 25;                   //设置端口,必须要整形型
  try
    ZkyIdSMTP.Connect;                    //连接服务器
  except                               //如果连接失败,提示并退出程序  
    ShowMessage('连接服务器失败');  
    Exit;
  end;
  ShowMessage('成功连接服务器。'+#10#13+'点击确认后准备发邮件。');  
  
  
  //------------------------------------
  //配置发送的内容,格式为:
  //<TIdMessage的控件名> . <属性>:=  
  //------------------------------------
  ZkyIdMessage.From.name := '1111@qq.com';//设置发件人姓名
  ZkyIdmessage.Recipients.EMailAddresses:='2222@qq.com';//设置收件人邮箱地址(此为单发邮件的语句,群发时需去掉)
  Zkyidmessage.Subject := '这是邮件的主题'; //设置邮件主题
  Zkyidmessage.body.text := '这是邮件的内容';//设置邮件内容  
  
  
  //------------------------------------  
  //群发邮件:
  //ZkyIdMessage.Recipients.add.Text:='<收件人邮箱>';
  //ZkyIdMessage.BCCList.Add.Address := '<抄送人邮箱>' ;
  //ZkyIdMessage.CCList.Add.Address := '<密送人邮箱>' ;
  //发送给多人的时候,多打几遍语句。
  //------------------------------------
  
  
  try  //发邮件
    ZkyIdSMTP.Send(ZkyIdMessage);
    Showmessage('邮件发送成功!');  
  except
    Showmessage('邮件发送失败!');
  end;
 
 
end;

————————————————
版权声明:本文为CSDN博主「楠珂伊梦千里月」的原创文章,遵循 CC 4.0 BY-SA 版权协议,转载请附上原文出处链接及本声明。
原文链接:https://blog.csdn.net/zhouky1993/article/details/60953892
引用 2 楼 老大i 的回复:
https://blog.csdn.net/zhouky1993/article/details/60953892
必须把登录密码也写进去吗?不登录能发送吗?
ooolinux 2019-09-16
  • 打赏
  • 举报
回复
引用 5 楼 早打大打打核战争 的回复:
把TIdSMTP->Host设置为收件人的邮件服务器的主机即可,可以用Indy的DNS解析组件获取
我找了一个老帖子的代码,不过差别有点大: https://bbs.csdn.net/topics/20453816 老概有成功的代码吗?能直接发到qq邮箱或者163邮箱都可以的。
加载更多回复(3)
本站地址:http://www.myuan.cn 梦缘网络站长:磊磊 QQ:77654031 本站源码是原乔客内核,经过中青的美化以及本站添加的插件,优化了代码.使浏览速度加快!(注:在许多地方的网上上的中青网的源码虽然文件完整,但是打开速度极慢.本人试过在自己的机子上用了一个半小时才打开!最快用了半个小时.不是吹牛!~)首页调用我模仿了中青网的样式. 只为大家提供一个模版,要其他模版的到本站论坛里发贴提供.同时还向大家提供采集模版! 也可定制!加入社区(在线文苑,在线测试,天气查询,彩票中心,WISH祝福),在线翻译, 彩信下载,韩国网站推荐. 如有想加其他插件,本站会帮助安装和调试. 以下是源码功能介绍新增]网站系统模板功能,包括新闻、文章、下载、电影、商城、论坛、整站格式模板设置;[新增]网站系统模板功能后台编辑预览功能(模板可视编辑器),包括新闻、文章、下载、商城、论坛、整站格式模板设置;[新增]强化了网站系统头部基数设置系统;强化了搜索引擎的搜索功能,让您的网站从此不在为无法被搜索引擎收录而发愁![新增]I P锁定功能,包括锁定I P,解锁,临时锁定、长久锁定;[新增]加强网络安全,同一帐号同一时间不能重复登陆,前台强行登陆IP记录与自动锁定,后台便捷控制;[新增]限制(或指定)用户I P登陆功能,查看用户最后登陆I P地址功能;[新增]加强商城金币支付功能系统,金币支付清单管理系统;[新增]商城商品报价系统,商品销售排行系统,商城开放购物开关; [新增]音乐频道模块,包含歌手管理系统,歌曲管理系统,专辑管理系统,参数配置系统,点歌管理系统,收藏管理系统;[新增]跳蚤市场模块;[新增]在线影院模块,包含电影管理与参数配置系统;[新增]交友与聚会系统功能;[新增]论坛树型显示结构功能;[新增]红包功能;[新增]网站顶部菜单自动生成双行功能;[新增]2.0与我在线功能,两种模式自由切换;[新增]分论坛在线显示功能;[新增]论坛帖子类型自动识别功能;[新增]添加固顶分页;[新增]添加数据库备份;[新增]加强了电影频道以及播放器功能,4:3与16:9屏幕自由切换;影音格式智能识别,解决了RM等问题;[新增]2.0的搜索模式;[新增]管理权限突破25,达到50,这样方便频道、插件过多用户设置权限;[新增]加强了会员管理系统,加强了上传管理系统与上传管理的预览功能;[新增]增加了会员信息查看功能;[新增]增加了会员个人文集系统功能;[新增]站内短信后台管理查看短信功能;[新增]站内短信奖励功能,站内短信群发功能;[新增]2.0 RSS管理系统,包括站内其他模块,论坛RSS功能;[新增]上传图片自动缩略图以及自动添加“图片水印”功能;[新增]完美结合了EWEBEDITOR编辑器,使HTML编辑功能更加强大(远程图片自动存储);[新增]增加了会员日记功能系统;[新增]网站内容报错接口;[新增]网站内容金币管理与控制接口(浏览文章、新闻等内容是否扣虚拟货币);[新增]全面的缓存处理系统;[新增]网站内容自动采集系统,包括新闻采集、文章采集,可以扩展到图片、素材等图文采集;[新增]论坛增加“魔法表情”功能;[新增]论坛帖子楼层颜色渐变,论坛HTML后台控制、生成与打印功能(支持HTML);[新增]增加注册时自动生成生日功能,解决大部分注册会员不填写生日所引起的问题;[新增]增加了人才市场系统功能;[新增]批量增加会员卡功能;[新增]新闻、文章模块标题加色,完美长文章分页功能(自动和手动,后台自由控制);[新增]论坛特殊贴功能,包括论坛背景音乐、回复贴、金钱贴、积分贴、购买贴、指定贴等;[新增]论坛不可回复,不可编辑时间参数设置,论坛发贴最短长度参数设置,论坛生日,导航显示开关参数设置;[新增]UBB错误修复与功能处理加强(加入UBBTST文件辅助处理)[新增]单个论坛参数设置,论坛版主管理系统;[新增]认证论坛功能系统;[新增]论坛回收站功能系统;[新增]用户自选风格开关、自定义头像、邮件修改、头衔修改开关等;[新增]自助添加友情链接;[新增]完美结合了智能化广告管理系统,让您不在为广告的管理而发愁,同时具备客户广告代理发布的技术基础(广告管理系统包括:客户信息登记、广告代码自动生成、固定BANNER广告、对联广告、浮游广告、渐隐广告、对话框广告、打开新窗口等9大广告显示模式、自定义广告开始时间与结束时间、广告最大显示量或点击量,可选择由哪种情况决定暂停广告播放、周设置,可在每周固定某天显示广告、自由控制广告状态、调整广告显示比率、内嵌上传系统,智能添加广告源文件链接、系统自动记录用户操作,方便监督工作、可以独立设置用户权限,保证系统安全、在线问题表单提交与追踪。。。。。。[新增]完美文章、新闻、论坛打印输出功能;[新增]无限服务器下载设置功能;[新增]会员网络相册管理功能;后台登陆:admin.asp 用户名:梦缘网络 密码: myuan myuan 如果有问题请和本站联系,当然如果你能更好的美化这个源码不要忘了告诉本站!好的东西大家一起分享,希望大家一起进步!谢谢使用本站程序.!
描述 android计步器的实现,自定义的一个弧形进度条,记步通过手机的传感器来实现,也就是说不支持传感器的机子(应该很老的了吧)就没有效果。看看效果图: 这里写图片描述这里写图片描述 自定义View public class StepView extends View { /** * 圆弧的宽度 */ private float borderWidth = dipToPx(10); /** * 画步数的数值的字体大小 */ private float numberTextSize = 0; /** * 步数 */ private String stepNumber = "0"; /** * 开始绘制圆弧的角度 */ private float startAngle = 125; /** * 终点对应的角度和起始点对应的角度的夹角 */ private float angleLength = 290; /** * 所要绘制的当前步数的蓝色圆弧终点到起点的夹角 */ private float currentAngleLength = 0; /** * 动画时长 */ private int animationLength = 3000; /** * 当前运动类型 */ private String type = "Riding"; /** * 当前活跃等级 */ private String level = "等级:轻度活跃"; /** * 步数上方文字 */ private String today = "今日步数"; /** * 单位km是否显示 */ private String unit = "Km"; public StepView(Context context) { super(context); } public StepView(Context context, AttributeSet attrs) { super(context, attrs); } public StepView(Context context, AttributeSet attrs, int defStyleAttr) { super(context, attrs, defStyleAttr); } @Override protected void onDraw(Canvas canvas) { super.onDraw(canvas); /**中心点的x坐标*/ float centerX = (getWidth()) / 2; /**指定圆弧的外轮廓矩形区域*/ RectF rectF = new RectF(0 + borderWidth, borderWidth, 2 * centerX - borderWidth, 2 * centerX - borderWidth); /**【第一步】绘制整体的灰色圆弧*/ drawArcYellow(canvas, rectF); /**【第二步】绘制当前进度的蓝色圆弧*/ drawArcRed(canvas, rectF); /**【第三步】绘制当前进度的白色数字*/ drawTextNumber(canvas, centerX); /**【第四步】绘制"本次步数"的灰色文字*/ drawTextStepString(canvas, centerX); /**【第五步】绘制当前记步类型*/ drawTextType(canvas, centerX); /**【第六步】绘制当前等级类型*/ drawTextLevel(canvas, centerX); /**【第七步】绘制骑行距离单位*/ drawTextUnit(canvas, centerX); } /** * 1.绘制总步数的灰色圆弧 * * @param canvas 画笔 * @param rectF 参考的矩形 */ private void drawArcYellow(Canvas canvas, RectF rectF) { Paint paint = new Paint(); /** 默认画笔颜色,灰色 */ paint.setColor(getResources().getColor(R.color.near_black)); /** 结合处为圆弧*/ paint.setStrokeJoin(Paint.Join.MITER); /** 设置画笔的样式 Paint.Cap.Round ,Cap.SQUARE等分别为圆形、方形*/ paint.setStrokeCap(Paint.Cap.BUTT); /** 设置画笔的填充样式 Paint.Style.FILL :填充内部;Paint.Style.FILL_AND_STROKE :填充内部和描边; Paint.Style.STROKE :仅描边*/ paint.setStyle(Paint.Style.STROKE); float[] floats = {4,16,4,16}; paint.setPathEffect(new DashPathEffect(floats, 0)); /**抗锯齿功能*/ paint.setAntiAlias(true); /**设置画笔宽度*/ paint.setStrokeWidth(borderWidth); /**绘制圆弧的方法 * drawArc(RectF oval, float startAngle, float sweepAngle, boolean useCenter, Paint paint)//画弧, 参数一是RectF对象,一个矩形区域椭圆形的界限用于定义在形状、大小、电弧, 参数二是起始角(度)在电弧的开始,圆弧起始角度,单位为度。 参数三圆弧扫过的角度,顺时针方向,单位为度,从右中间开始为零度。 参数四是如果这是true(真)的话,在绘制圆弧时将圆心包括在内,通常用来绘制扇形;如果它是false(假)这将是一个弧线, 参数五是Paint对象; */ canvas.drawArc(rectF, startAngle, angleLength, false, paint); } /** * 2.绘制当前步数的蓝色圆弧 */ private void drawArcRed(Canvas canvas, RectF rectF) { Paint paintCurrent = new Paint(); paintCurrent.setStrokeJoin(Paint.Join.MITER); paintCurrent.setStrokeCap(Paint.Cap.BUTT);//圆角弧度 paintCurrent.setStyle(Paint.Style.STROKE);//设置填充样式 paintCurrent.setAntiAlias(true);//抗锯齿功能 paintCurrent.setStrokeWidth(borderWidth);//设置画笔宽度 paintCurrent.setColor(getResources().getColor(R.color.colorPrimary));//设置画笔颜色 float[] floats = {4,16,4,16}; paintCurrent.setPathEffect(new DashPathEffect(floats, 0)); canvas.drawArc(rectF, startAngle, currentAngleLength, false, paintCurrent); } /** * 3.圆环中心的步数 */ private void drawTextNumber(Canvas canvas, float centerX) { Paint vTextPaint = new Paint(); vTextPaint.setTextAlign(Paint.Align.CENTER); vTextPaint.setAntiAlias(true);//抗锯齿功能 vTextPaint.setTextSize(numberTextSize); Typeface font = Typeface.create(Typeface.SANS_SERIF, Typeface.NORMAL); vTextPaint.setTypeface(font);//字体风格 vTextPaint.setColor(getResources().getColor(R.color.white)); Rect bounds_Number = new Rect(); vTextPaint.getTextBounds(stepNumber, 0, stepNumber.length(), bounds_Number); canvas.drawText(stepNumber, centerX, getHeight() / 2 + bounds_Number.height() / 2, vTextPaint); } /** * 4.圆环中心[本次步数]的文字 */ private void drawTextStepString(Canvas canvas, float centerX) { Paint vTextPaint = new Paint(); vTextPaint.setTextSize(dipToPx(16)); vTextPaint.setTextAlign(Paint.Align.CENTER); vTextPaint.setAntiAlias(true);//抗锯齿功能 vTextPaint.setColor(getResources().getColor(R.color.gray)); Rect bounds = new Rect(); vTextPaint.getTextBounds(today, 0, today.length(), bounds); canvas.drawText(today, centerX, getHeight() / 2 + bounds.height() - 2 * getFontHeight(numberTextSize), vTextPaint); } /** * 5.圆环中下[Walking]等文字 */ private void drawTextType(Canvas canvas, float centerX) { Paint mTypePaint = new Paint(); mTypePaint.setTextSize(dipToPx(22)); mTypePaint.setTextAlign(Paint.Align.CENTER); mTypePaint.setAntiAlias(true); mTypePaint.setColor(getResources().getColor(R.color.text_blue)); Rect bounds = new Rect(); mTypePaint.getTextBounds(type, 0, type.length(), bounds); canvas.drawText(type, centerX, getHeight() / 2 + 2 * bounds.height() + getFontHeight(numberTextSize), mTypePaint); } /** * 6.绘制圆环下方等级 */ private void drawTextLevel(Canvas canvas, float centerX) { Paint mLevelPaint = new Paint(); mLevelPaint.setTextSize(dipToPx(12)); mLevelPaint.setTextAlign(Paint.Align.CENTER); mLevelPaint.setAntiAlias(true); mLevelPaint.setColor(getResources().getColor(R.color.input_hint_gray)); Rect bounds = new Rect(); mLevelPaint.getTextBounds(level, 0, level.length(), bounds); canvas.drawText(level, centerX, getHeight() / 2 + 2 * bounds.height() + 2 * getFontHeight(numberTextSize), mLevelPaint); } /** * 7.绘制骑行单位km */ private void drawTextUnit(Canvas canvas, float centerX) { Paint mUnitPaint = new Paint(); mUnitPaint.setTextSize(dipToPx(16)); mUnitPaint.setTextAlign(Paint.Align.CENTER); mUnitPaint.setAntiAlias(true); mUnitPaint.setColor(getResources().getColor(R.color.input_hint_gray)); Rect bounds = new Rect(); mUnitPaint.getTextBounds(unit, 0, unit.length(), bounds); canvas.drawText(unit, centerX+ stepNumber.length()*80, getHeight() / 2 + bounds.height() * 3 / 2, mUnitPaint); } /** * 获取当前步数的数字的高度 * * @param fontSize 字体大小 * @return 字体高度 */ public int getFontHeight(float fontSize) { Paint paint = new Paint(); paint.setTextSize(fontSize); Rect bounds_Number = new Rect(); paint.getTextBounds(stepNumber, 0, stepNumber.length(), bounds_Number); return bounds_Number.height(); } /** * dip 转换成px * * @param dip * @return */ private int dipToPx(float dip) { float density = getContext().getResources().getDisplayMetrics().density; return (int) (dip * density + 0.5f * (dip >= 0 ? 1 : -1)); } /** * 所走的步数进度 * @param totalStepNum 设置的步数 * @param currentCounts 所走步数 */ public void setCurrentCount(int totalStepNum, int currentCounts) { /**如果当前走的步数超过总步数则圆弧还是270度,不能成为园*/ if (currentCounts > totalStepNum) { currentCounts = totalStepNum; } /**上次所走步数占用总共步数的百分比*/ float scalePrevious = (float) Integer.valueOf(stepNumber) / totalStepNum; /**换算成弧度最后要到达的角度的长度-->弧长*/ float previousAngleLength = scalePrevious * angleLength; /**所走步数占用总共步数的百分比*/ float scale = (float) currentCounts / totalStepNum; /**换算成弧度最后要到达的角度的长度-->弧长*/ float currentAngleLength = scale * angleLength; /**开始执行动画*/ setAnimation(previousAngleLength, currentAngleLength, animationLength); stepNumber = String.valueOf(currentCounts); setTextSize(currentCounts); } /** * 设置各个参数 */ public void setParams(String today, String unit, String type, String level) { this.today = today; this.unit = unit; this.type = type; this.level = level; } /** * 为进度设置动画 * ValueAnimator是整个属性动画机制当中最核心的一个类,属性动画的运行机制是通过不断地对值进行操作来实现的, * 而初始值和结束值之间的动画过渡就是由ValueAnimator这个类来负责计算的。 * 它的内部使用一种时间循环的机制来计算值与值之间的动画过渡, * 我们只需要将初始值和结束值提供给ValueAnimator,并且告诉它动画所需运行的时长, * 那么ValueAnimator就会自动帮我们完成从初始值平滑地过渡到结束值这样的效果。 * * @param start 初始值 * @param current 结束值 * @param length 动画时长 */ private void setAnimation(float start, float current, int length) { ValueAnimator progressAnimator = ValueAnimator.ofFloat(start, current); progressAnimator.setDuration(length); progressAnimator.setTarget(currentAngleLength); progressAnimator.addUpdateListener(new ValueAnimator.AnimatorUpdateListener() { @Override public void onAnimationUpdate(ValueAnimator animation) { /**每次在初始值和结束值之间产生的一个平滑过渡的值,逐步去更新进度*/ currentAngleLength = (float) animation.getAnimatedValue(); invalidate(); } }); progressAnimator.start(); } /** * 设置文本大小,防止步数特别大之后放不下,将字体大小动态设置 * * @param num */ public void setTextSize(int num) { String s = String.valueOf(num); int length = s.length(); if (length 4 && length 6 && length 8) { numberTextSize = dipToPx(25); } } } 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 167 168 169 170 171 172 173 174 175 176 177 178 179 180 181 182 183 184 185 186 187 188 189 190 191 192 193 194 195 196 197 198 199 200 201 202 203 204 205 206 207 208 209 210 211 212 213 214 215 216 217 218 219 220 221 222 223 224 225 226 227 228 229 230 231 232 233 234 235 236 237 238 239 240 241 242 243 244 245 246 247 248 249 250 251 252 253 254 255 256 257 258 259 260 261 262 263 264 265 266 267 268 269 270 271 272 273 274 275 276 277 278 279 280 281 282 283 284 285 286 287 288 289 290 291 292 293 294 295 296 297 298 299 300 301 302 303 304 305 306 307 308 309 310 311 312 313 314 315 316 317 318 319 320 321 整个View的代码逻辑还是比较清晰的,自定义的view会首先调用onDraw()方法,获取了整个布局的中心和轮廓,然后开始七步分别绘制控件,这就是大家可以发挥的部分了,想怎么设计怎么设计。 第一步着重看一下,这个虚线的实现: float[] floats = {4,16,4,16}; paint.setPathEffect(new DashPathEffect(floats, 0)); 1 2 floats的四个参数意思是{画宽度,间隔宽度,画宽度,间隔宽度},这里的floats是数组也就是说你可以把每一个宽度和间隔都写出来,当然如果一样的话你可以只列出两个,系统会自动识别来延续后面的宽度和间隔。 因为注释很清楚,其他的这里就不详述了,想改啥基本都能看明白。 记步服务 public class StepService extends Service implements SensorEventListener { private String TAG = "StepService"; /** * 默认为10秒进行一次存储 */ private static int duration = 10 * 1000; /** * 传感器管理对象 */ private SensorManager sensorManager; /** * 保存记步计时器 */ private TimeCount time; /** * 当前所走的步数 */ private int CURRENT_STEP; /** * 计步传感器类型 Sensor.TYPE_STEP_COUNTER或者Sensor.TYPE_STEP_DETECTOR */ private static int stepSensorType = -1; /** * 每次第一次启动记步服务时是否从系统中获取了已有的步数记录 */ private boolean hasRecord = false; /** * 系统中获取到的已有的步数 */ private int hasStepCount = 0; /** * 上一次的步数 */ private int previousStepCount = 0; /** * IBinder对象,向Activity传递数据的桥梁 */ private StepBinder stepBinder = new StepBinder(); @Override public void onCreate() { super.onCreate(); Log.d(TAG, "onCreate()"); new Thread(new Runnable() { public void run() { startStepDetector(); } }).start(); startTimeCount(); } /** * 开始保存记步数据 */ private void startTimeCount() { if (time == null) { time = new TimeCount(duration, 1000); } time.start(); } /** * UI监听器对象 */ private UpdateUiCallBack mCallback; /** * 注册UI更新监听 * * @param paramICallback */ public void registerCallback(UpdateUiCallBack paramICallback) { this.mCallback = paramICallback; } @Override public IBinder onBind(Intent intent) { return stepBinder; } /** * 向Activity传递数据的纽带 */ public class StepBinder extends Binder { /** * 获取当前service对象 * * @return StepService */ public StepService getService() { return StepService.this; } } /** * 获取当前步数 * * @return */ public int getStepCount() { return CURRENT_STEP; } @Override public void onStart(Intent intent, int startId) { super.onStart(intent, startId); } @Override public int onStartCommand(Intent intent, int flags, int startId) { return START_STICKY; } /** * 获取传感器实例 */ private void startStepDetector() { if (sensorManager != null) { sensorManager = null; } // 获取传感器管理器的实例 sensorManager = (SensorManager) this .getSystemService(SENSOR_SERVICE); //android4.4以后可以使用计步传感器 addCountStepListener(); } /** * 添加传感器监听 * 1. TYPE_STEP_COUNTER API的解释说返回从开机被激活后统计的步数,当重启手机后该数据归零, * 该传感器是一个硬件传感器所以它是低功耗的。 * 为了能持续的计步,请不要反注册事件,就算手机处于休眠状态它依然会计步。 * 当激活的时候依然会上报步数。该sensor适合在长时间的计步需求。 * * 2.TYPE_STEP_DETECTOR翻译过来就是走路检测, * API文档也确实是这样说的,该sensor只用来监监测走步,每次返回数字1.0。 * 如果需要长事件的计步请使用TYPE_STEP_COUNTER。 */ private void addCountStepListener() { Sensor countSensor = sensorManager.getDefaultSensor(Sensor.TYPE_STEP_COUNTER); Sensor detectorSensor = sensorManager.getDefaultSensor(Sensor.TYPE_STEP_DETECTOR); if (countSensor != null) { stepSensorType = Sensor.TYPE_STEP_COUNTER; Log.v(TAG, "Sensor.TYPE_STEP_COUNTER"); sensorManager.registerListener(StepService.this, countSensor, SensorManager.SENSOR_DELAY_NORMAL); } else if (detectorSensor != null) { stepSensorType = Sensor.TYPE_STEP_DETECTOR; Log.v(TAG, "Sensor.TYPE_STEP_DETECTOR"); sensorManager.registerListener(StepService.this, detectorSensor, SensorManager.SENSOR_DELAY_NORMAL); } } /** * 传感器监听回调 * @param event */ @Override public void onSensorChanged(SensorEvent event) { if (stepSensorType == Sensor.TYPE_STEP_COUNTER) { //获取当前传感器返回的临时步数 int tempStep = (int) event.values[0]; //首次如果没有获取手机系统中已有的步数则获取一次系统中APP还未开始记步的步数 if (!hasRecord) { hasRecord = true; hasStepCount = tempStep; } else { //获取APP打开到现在的总步数=本次系统回调的总步数-APP打开之前已有的步数 int thisStepCount = tempStep - hasStepCount; //本次有效步数=(APP打开后所记录的总步数-上一次APP打开后所记录的总步数) int thisStep = thisStepCount - previousStepCount; //总步数=现有的步数+本次有效步数 CURRENT_STEP += (thisStep); //记录最后一次APP打开到现在的总步数 previousStepCount = thisStepCount; } } else if (stepSensorType == Sensor.TYPE_STEP_DETECTOR) { if (event.values[0] == 1.0) { CURRENT_STEP++; } } } @Override public void onAccuracyChanged(Sensor sensor, int accuracy) { } /** * 保存记步数据 */ class TimeCount extends CountDownTimer { public TimeCount(long millisInFuture, long countDownInterval) { super(millisInFuture, countDownInterval); } @Override public void onFinish() { // 如果计时器正常结束,则开始计步 time.cancel(); mCallback.updateUi(CURRENT_STEP); startTimeCount(); } @Override public void onTick(long millisUntilFinished) { } } @Override public void onDestroy() { super.onDestroy(); //取消前台进程 stopForeground(true); } @Override public boolean onUnbind(Intent intent) { return super.onUnbind(intent); } } 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 167 168 169 170 171 172 173 174 175 176 177 178 179 180 181 182 183 184 185 186 187 188 189 190 191 192 193 194 195 196 197 198 199 200 201 202 203 204 205 206 207 208 209 210 211 212 213 214 215 216 217 218 219 220 221 222 223 224 225 226 227 因为记步是在后台进行的,开启之后我们可以干其他事情,所以采用服务的方式,而我们需要获取记步的数据,所以要通过绑定的方式来注册服务,这样才能获得通信用的connection。逻辑思路是,通过计步器来获取步数,然后设置监听器来监听步数的改变,如果步数改变更新步数的变量,然后开启一个计时器来定时去记录,设置回调参数来返回步数。回调接口只有一个方法: public interface UpdateUiCallBack { /** * 更新UI步数 * * @param stepCount 步数 */ void updateUi(int stepCount); } 1 2 3 4 5 6 7 8 当然别忘了在AndroidManifest.xml中注册Service: 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 还有记步需要用的权限: 1 2 3 调用MainActivity public class MainActivity extends AppCompatActivity { private StepView stepView; private Button button; private boolean isBind = false; @Override protected void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); setContentView(R.layout.activity_main); initView(); initData(); } private void initView() { stepView = (StepView) findViewById(R.id.step_walk_arv); button = (Button) findViewById(R.id.begin_btn); button.setOnClickListener(new View.OnClickListener() { @Override public void onClick(View v) { begin(); } }); } private void initData() { stepView.setParams("今日步数", "", "Walking", "等级:轻度活跃"); stepView.setCurrentCount(1000,0); } private void begin() { if (isBind == false){ //开启记步 Intent intent = new Intent(this, StepService.class); isBind = bindService(intent, conn, Context.BIND_AUTO_CREATE); startService(intent); button.setText("正在记步"); } } /** * 用于查询应用服务(application Service)的状态的一种interface, * 更详细的信息可以参考Service 和 context.bindService()中的描述, * 和许多来自系统的回调方式一样,ServiceConnection的方法都是进程的主线程中调用的。 */ ServiceConnection conn = new ServiceConnection() { /** * 在建立起于Service的连接时会调用方法,目前Android是通过IBind机制实现与服务的连接。 * @param name 实际所连接到的Service组件名称 * @param service 服务的通信信道的IBind,可以通过Service访问对应服务 */ @Override public void onServiceConnected(ComponentName name, IBinder service) { StepService stepService = ((StepService.StepBinder) service).getService(); //设置初始化数据 stepView.setCurrentCount(1000, stepService.getStepCount()); //设置步数监听回调 stepService.registerCallback(new UpdateUiCallBack() { @Override public void updateUi(int stepCount) { stepView.setCurrentCount(1000, stepCount); } }); } /** * 当与Service之间的连接丢失的时候会调用方法, * 这种情况经常发生在Service所在的进程崩溃或者被Kill的时候调用, * 此方法不会移除与Service的连接,当服务重新启动的时候仍然会调用 onServiceConnected()。 * @param name 丢失连接的组件名称 */ @Override public void onServiceDisconnected(ComponentName name) { } }; //服务解绑 @Override public void onDestroy() { super.onDestroy(); if (isBind) { unbindService(conn); isBind = false; } } } 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 调用也很简单,首先在布局文件中引用StepView,然后初始化,设置圆弧里面的参数,设置总步数和当前步数(开始当然为0): stepView.setParams("今日步数", "", "Walking", "等级:轻度活跃"); stepView.setCurrentCount(1000,0); 1 2 其中还设置了一个boolean值isBind区分是否已经启动服务并绑定到了activity上,以便在onDestroy()方法是解除绑定。 整个计步器的实现还是相对简单的,之前还想过要在圆弧圈外面画一个手指来指向当前的进度,不过暂时还没想通怎么实现,有兴趣的小伙伴研究之后告诉一声。

1,593

社区成员

发帖
与我相关
我的任务
社区描述
Delphi 网络通信/分布式开发
社区管理员
  • 网络通信/分布式开发社区
加入社区
  • 近7日
  • 近30日
  • 至今
社区公告
暂无公告

试试用AI创作助手写篇文章吧