第二篇-用Flutter手撸一个抖音国内版,看看有多炫

【拿走不谢】flutter3.19仿抖音实战短视频源码 flutter3.19.x仿抖音教你开发商业级APP接入字节跳动穿山甲广告//穿山甲广告 Android appid 必填//穿山甲广告 ios appid 必填//使用TextureView控件播放视频,默认为SurfaceView,当有SurfaceView冲突的场景,可以使用TextureView 选填//appname 必填//是否允许sdk展示通知栏提示 选填//是否在锁屏场景支持展示广告落地页 选填//测试阶段打开,可以通过日志排查问题,上线时去除该调用 选太难。 阅读详情

前言

继上一篇使用Flutter开发的抖音国际版 后再次撸一个国内版抖音,大部分功能已完成,主要是Flutter开发APP速度很爽,  先看下图

 

项目主要结构介绍

 

 这次主要的改动在api.dart 及douyin.dart里,国内抖音的api是不同的,另外地址以及实体类也不一样。详细下面介绍.

抖音的实体类

主要是讲json转化为实体模型,然后绑定到view 层面,写这个实体类还是很耗费时间的,毕竟得先用爬网知识讲抖音的json拿到,并且根据json反向写实体类.

这个地址获取到抖音的推荐列表json: https://creator.douyin.com/aweme/v1/creator/data/billboard/?billboard_type=4,绑定第一个Douyin实体,拿到所有抖音的url地址

 

通过第二个api地址 https://www.iesdouyin.com/web/api/v2/aweme/iteminfo/?item_ids= 拿到抖音视频所有的json信息并且绑定VideoData实体类

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

322

323

324

325

326

327

328

329

330

331

332

333

334

335

336

337

338

339

340

341

342

343

344

345

346

347

348

349

350

351

352

353

354

355

356

357

358

359

360

361

362

363

364

365

366

367

368

369

370

371

372

373

374

375

376

377

378

379

380

381

382

383

384

385

386

387

388

389

390

391

392

393

394

395

396

397

398

399

400

401

402

403

404

405

406

407

408

409

410

411

412

413

414

415

416

417

418

419

420

421

422

423

424

425

426

427

428

429

430

431

432

433

434

435

436

437

438

439

440

441

442

443

444

445

446

447

448

449

450

451

452

453

454

455

456

457

458

459

460

461

462

463

464

465

466

467

468

469

470

471

472

473

474

475

476

477

478

479

480

481

482

483

484

485

486

487

488

489

490

491

492

493

494

495

496

497

498

499

500

501

502

503

504

505

506

507

508

509

510

511

512

513

514

515

516

517

518

519

520

521

522

523

524

525

526

527

528

529

530

531

532

533

534

535

536

537

538

539

540

541

542

543

544

545

546

547

548

549

550

551

552

553

554

555

556

557

558

559

560

561

562

563

564

565

566

567

568

569

570

571

572

573

574

575

576

577

578

579

580

581

582

583

584

585

586

587

588

589

590

591

592

593

594

595

596

597

598

599

600

601

602

603

604

605

606

607

608

609

610

611

612

613

614

615

616

617

618

619

620

621

622

623

624

625

626

627

628

629

630

631

632

633

634

635

636

637

638

639

640

641

642

643

644

645

646

647

648

649

650

651

652

653

654

655

656

657

658

659

660

661

662

663

664

665

666

667

668

669

670

671

672

673

674

675

676

677

678

679

680

681

682

683

684

685

686

687

688

689

690

691

692

693

694

695

696

697

698

699

700

701

702

703

704

705

706

707

708

709

710

711

712

713

714

715

716

717

718

719

720

721

722

723

724

725

726

727

728

729

730

731

732

733

734

735

736

737

738

739

740

741

742

743

744

745

746

747

748

749

750

751

752

753

754

755

756

757

758

759

760

761

762

763

764

765

766

767

768

769

770

771

772

773

774

775

776

777

778

779

780

781

782

783

784

785

786

787

788

789

790

791

792

793

794

795

796

797

798

799

800

801

802

803

804

805

806

807

808

809

810

811

812

813

814

815

816

817

818

819

820

821

822

823

824

825

826

827

828

829

830

831

832

833

834

835

836

837

838

839

840

841

842

843

844

845

846

847

848

849

850

851

852

853

854

855

856

857

858

859

860

861

862

863

864

865

866

867

868

869

870

871

872

873

874

875

876

877

878

879

880

881

882

883

884

885

886

class Douyin {

  int statusCode;

  String statusMsg;

  List<BillboardData> billboardData;

  Extra extra;

 

  Douyin({this.statusCode, this.statusMsg, this.billboardData, this.extra});

 

  Douyin.fromJson(Map<String, dynamic> json) {

    statusCode = json["status_code"];

    statusMsg = json["status_msg"];

    if (json['billboard_data'] != null) {

      billboardData = new List<BillboardData>();

      json["billboard_data"].forEach((v) {

        billboardData.add(BillboardData.fromJson(v));

      });

    }

    extra = json['extra'] != null ? new Extra.fromJson(json['extra']) : null;

  }

 

  Map<String, dynamic> toJson() {

    final Map<String, dynamic> data = new Map<String, dynamic>();

    data["status_code"] = this.statusCode;

    data["status_msg"] = this.statusMsg;

    if (this.billboardData != null) {

      data["billboard_data"] =

          this.billboardData.map((v) => v.toJson()).toList();

    }

    data["extra"] = this.extra;

    return data;

  }

}

 

class BillboardData {

  String author;

  String imgUrl;

  String link;

  int rank;

  String title;

  String value;

 

  BillboardData(

      {this.author, this.imgUrl, this.link, this.rank, this.title, this.value});

 

  BillboardData.fromJson(Map<String, dynamic> json) {

    this.author = json["author"];

    this.imgUrl = json["img_url"];

    this.link = json["link"];

    this.rank = json["rank"];

    this.title = json["title"];

    this.value = json["value"];

  }

 

  Map<String, dynamic> toJson() {

    final Map<String, dynamic> data = new Map<String, dynamic>();

    data["author"] = this.author;

    data["img_url"] = this.imgUrl;

    data["link"] = this.link;

    data["rank"] = this.rank;

    data["title"] = this.title;

    data["value"] = this.value;

    return data;

  }

}

 

class Extra {

  int now;

  Extra({this.now});

 

  Extra.fromJson(Map<String, dynamic> json) {

    now = json["now"];

  }

 

  Map<String, dynamic> toJson() {

    final Map<String, dynamic> data = new Map<String, dynamic>();

    data["now"] = this.now;

    return data;

  }

}

 

class VideoData {

  int statusCode;

  List<Itemlist> itemList;

  ExtraData extra;

 

  VideoData.fromJson(Map<String, dynamic> json) {

    this.statusCode = json["status_code"];

    if (json['item_list'] != null) {

      itemList = new List<Itemlist>();

      json['item_list'].forEach((v) {

        itemList.add(new Itemlist.fromJson(v));

      });

    }

    this.extra =

        json["extra"] != null ? new ExtraData.fromJson(json["extra"]) : null;

  }

 

  Map<String, dynamic> toJson() {

    final Map<String, dynamic> data = new Map<String, dynamic>();

    data["status_code"] = this.statusCode;

    if (this.itemList != null) {

      data['item_list'] = this.itemList.map((v) => v.toJson()).toList();

    }

    if (this.extra != null) {

      data["extra"] = this.extra.toJson();

    }

    return data;

  }

}

 

class Itemlist {

  String awemeid;

  String videolabels;

  String labeltoptext;

  int category;

  Author author;

  int duration;

  String promotions;

  int ispreview;

  int createtime;

  List<ChaList> chalist;

  String commentlist;

  int authoruserid;

  String videotext;

  int groupid;

  bool islivereplay;

  ShareInfo shareinfo;

  String position;

  String imageinfos;

  RiskInfos riskinfos;

  String uniqidposition;

  String geofencing;

  Statistics statistics;

  int awemetype;

  Music music;

  List<TextExtra> textExtras;

  Video video;

  String shareurl;

  String desc;

  String longvideo;

 

  Itemlist(

      {this.awemeid,

      this.videolabels,

      this.labeltoptext,

      this.category,

      this.author,

      this.duration,

      this.promotions,

      this.ispreview,

      this.createtime,

      this.chalist,

      this.commentlist,

      this.authoruserid,

      this.videotext,

      this.groupid,

      this.islivereplay,

      this.shareinfo,

      this.position,

      this.imageinfos,

      this.riskinfos,

      this.uniqidposition,

      this.geofencing,

      this.statistics,

      this.awemetype,

      this.music,

      this.textExtras,

      this.video,

      this.shareurl,

      this.desc,

      this.longvideo});

 

  Itemlist.fromJson(Map<String, dynamic> json) {

    this.awemeid = json["aweme_id"];

    this.videolabels = json["video_labels"];

    this.labeltoptext = json["label_top_text"];

    this.category = json["category"];

    this.author =

        json["author"] != null ? new Author.fromJson(json["author"]) : null;

    this.duration = json["duration"];

    this.promotions = json["promotions"];

    this.ispreview = json["is_preview"];

    this.createtime = json["create_time"];

    if (json["cha_list"] != null) {

      this.chalist = new List<ChaList>();

      json["cha_list"].forEach((v) {

        this.chalist.add(new ChaList.fromJson(v));

      });

    }

    this.commentlist = json["comment_list"];

    this.authoruserid = json["author_user_id"];

    this.videotext = json["video_text"];

    this.groupid = json["group_id"];

    this.islivereplay = json["is_live_replay"];

    this.shareinfo = json["share_info"] != null

        ? new ShareInfo.fromJson(json["share_info"])

        : null;

    this.position = json["position"];

    this.imageinfos = json["image_infos"];

    this.riskinfos = json["risk_infos"] != null

        ? new RiskInfos.fromJson(json["risk_infos"])

        : null;

    this.uniqidposition = json["uniqid_position"];

    this.geofencing = json["geofencing"];

    this.statistics = json["statistics"] != null

        ? new Statistics.fromJson(json["statistics"])

        : null;

    this.awemetype = json["aweme_type"];

    this.music =

        json["music"] != null ? new Music.fromJson(json["music"]) : null;

    if (json["text_extra"] != null) {

      this.textExtras = new List<TextExtra>();

      json['text_extra'].forEach((v) {

        textExtras.add(new TextExtra.formJson(v));

      });

    }

    this.video =

        json["video"] != null ? new Video.fromJson(json["video"]) : null;

    this.shareurl = json["share_url"];

    this.desc = json["desc"];

    this.longvideo = json["long_video"];

  }

 

  Map<String, dynamic> toJson() {

    final Map<String, dynamic> data = new Map<String, dynamic>();

    data["aweme_id"] = this.awemeid;

    data["video_labels"] = this.videolabels;

    data["label_top_text"] = this.labeltoptext;

    data["category"] = this.category;

    if (this.author != null) {

      data["author"] = this.author.toJson();

    }

    data["duration"] = this.duration;

    data["promotions"] = this.promotions;

    data["is_preview"] = this.ispreview;

    data["create_time"] = this.createtime;

    if (this.chalist != null) {

      data["cha_list"] = this.chalist.map((e) => e.toJson()).toList();

    }

    data["comment_list"] = this.commentlist;

    data["author_user_id"] = this.authoruserid;

    data["video_text"] = this.videotext;

    data["group_id"] = this.groupid;

    data["is_live_replay"] = this.islivereplay;

    if (this.shareinfo != null) {

      data["share_info"] = this.shareinfo.toJson();

    }

    data["position"] = this.position;

    data["image_infos"] = this.imageinfos;

    if (this.riskinfos != null) {

      data["risk_infos"] = this.riskinfos.toJson();

    }

    data["uniqid_position"] = this.uniqidposition;

    data["geofencing"] = this.geofencing;

    if (this.statistics != null) {

      data["statistics"] = this.statistics.toJson();

    }

    data["aweme_type"] = this.awemetype;

    if (this.music != null) {

      data["music"] = this.music.toJson();

    }

    if (this.textExtras != null) {

      data["text_extra"] = this.textExtras.map((e) => e.toJson()).toList();

    }

    if (this.video != null) {

      data["video"] = this.video.toJson();

    }

    data["share_url"] = this.shareurl;

    data["desc"] = this.desc;

    data["long_video"] = this.longvideo;

    return data;

  }

}

 

class TextExtra {

  String hashtagName;

  int hashtagId;

  int start;

  int end;

  int type;

 

  TextExtra(

      {this.hashtagName, this.hashtagId, this.start, this.end, this.type});

 

  TextExtra.formJson(Map<String, dynamic> json) {

    this.hashtagName = json["hashtag_name"];

    this.hashtagId = json["hashtag_id"];

    this.start = json["start"];

    this.end = json["end"];

    this.type = json["type"];

  }

 

  Map<String, dynamic> toJson() {

    final Map<String, dynamic> data = new Map<String, dynamic>();

    data["hashtag_name"] = this.hashtagName;

    data["hashtag_id"] = this.hashtagId;

    data["start"] = this.start;

    data["end"] = this.end;

    data["type"] = this.type;

    return data;

  }

}

 

// Author

class Author {

  String geofencing;

  String uid;

  String shortID;

  String signature;

  AvatarMedium avatarMedium;

  String uniqueId;

  String followersDetail;

  String platformSyncInfo;

  String policyVersion;

  String nickname;

  AvatarLarger avatarlarger;

  AvatarThumb avatarthumb;

 

  Author(

      {this.geofencing,

      this.uid,

      this.shortID,

      this.signature,

      this.avatarMedium,

      this.uniqueId,

      this.followersDetail,

      this.platformSyncInfo,

      this.policyVersion,

      this.nickname,

      this.avatarlarger,

      this.avatarthumb});

 

  Author.fromJson(Map<String, dynamic> json) {

    this.geofencing = json["geofencing"];

    this.uid = json["uid"];

    this.shortID = json["short_id"];

    this.signature = json["signature"];

    this.avatarMedium = json["avatar_medium"] != null

        ? new AvatarMedium.fromJson(json["avatar_medium"])

        : null;

    this.uniqueId = json["unique_id"];

    this.followersDetail = json["followers_detail"];

    this.platformSyncInfo = json["platform_sync_info"];

    this.policyVersion = json["policy_version"];

    this.nickname = json["nickname"];

    this.avatarlarger = json["avatar_larger"] != null

        ? new AvatarLarger.formJson(json["avatar_larger"])

        : null;

    this.avatarthumb = json["avatar_thumb"] != null

        ? new AvatarThumb.formJson(json["avatar_thumb"])

        : null;

  }

 

  Map<String, dynamic> toJson() {

    final Map<String, dynamic> data = new Map<String, dynamic>();

    data["geofencing"] = this.geofencing;

    data["uid"] = this.uid;

    data["short_id"] = this.shortID;

    data["signature"] = this.signature;

    if (this.avatarMedium != null) {

      data["avatar_medium"] = this.avatarMedium.toJson();

    }

    data["unique_id"] = this.uniqueId;

    data["followers_detail"] = this.followersDetail;

    data["platform_sync_info"] = this.platformSyncInfo;

    data["policy_version"] = this.policyVersion;

    data["nickname"] = this.nickname;

    if (this.avatarlarger != null) {

      data["avatar_larger"] = this.avatarlarger.toJson();

    }

    if (this.avatarthumb != null) {

      data["avatar_thumb"] = this.avatarthumb.toJson();

    }

    return data;

  }

}

 

class AvatarMedium {

  List<String> urlList;

  String uri;

  AvatarMedium({this.urlList, this.uri});

 

  AvatarMedium.fromJson(Map<String, dynamic> json) {

    if (json["url_list"] != null) {

      this.urlList = json["url_list"].cast<String>();

    }

    this.uri = json["uri"];

  }

 

  Map<String, dynamic> toJson() {

    final Map<String, dynamic> data = new Map<String, dynamic>();

    data["url_list"] = this.urlList;

    return data;

  }

}

 

class AvatarLarger {

  String uri;

  List<String> urlList;

  AvatarLarger({this.uri, this.urlList});

 

  AvatarLarger.formJson(Map<String, dynamic> json) {

    this.uri = json["uri"] as String;

    this.urlList = json["url_list"].cast<String>();

  }

 

  Map<String, dynamic> toJson() {

    final Map<String, dynamic> data = new Map<String, dynamic>();

    data["url_list"] = this.urlList;

    data["uri"] = this.uri;

    return data;

  }

}

 

class AvatarThumb {

  String uri;

  List<String> urlList;

  AvatarThumb({this.uri, this.urlList});

 

  AvatarThumb.formJson(Map<String, dynamic> json) {

    this.uri = json["uri"] as String;

    this.urlList = json["url_list"].cast<String>();

  }

 

  Map<String, dynamic> toJson() {

    final Map<String, dynamic> data = new Map<String, dynamic>();

    data["url_list"] = this.urlList;

    data["uri"] = this.uri;

    return data;

  }

}

 

class ExtraData {

  int now;

  String logid;

  ExtraData({this.now, this.logid});

 

  ExtraData.fromJson(Map<String, dynamic> json) {

    this.now = json["now"];

    this.logid = json["logid"];

  }

 

  Map<String, dynamic> toJson() {

    final Map<String, dynamic> data = new Map<String, dynamic>();

    data["now"] = this.now;

    data["logid"] = this.logid;

    return data;

  }

}

//End author

 

//ChaList

class ChaList {

  String chaName;

  int viewCount;

  String hashTagProfile;

  bool isCommerce;

  String cid;

  String desc;

  int userCount;

  String connectMusic;

  int type;

  CoverItem coverItem;

  ChaList(

      {this.chaName,

      this.viewCount,

      this.hashTagProfile,

      this.isCommerce,

      this.cid,

      this.desc,

      this.userCount,

      this.connectMusic,

      this.type,

      this.coverItem});

 

  ChaList.fromJson(Map<String, dynamic> json) {

    this.chaName = json["cha_name"];

    this.viewCount = json["view_count"];

    this.hashTagProfile = json["hash_tag_profile"];

    this.isCommerce = json["is_commerce"];

    this.cid = json["cid"];

    this.desc = json["desc"];

    this.userCount = json["user_count"];

    this.connectMusic = json["connect_music"];

    this.type = json["type"];

    this.coverItem = json["cover_item"] != null

        ? new CoverItem.formJson(json["cover_item"])

        : null;

  }

 

  Map<String, dynamic> toJson() {

    final Map<String, dynamic> data = new Map<String, dynamic>();

    data["cha_name"] = this.chaName;

    data["view_count"] = this.viewCount;

    data["hash_tag_profile"] = this.hashTagProfile;

    data["is_commerce"] = this.isCommerce;

    data["cid"] = this.cid;

    data["desc"] = this.desc;

    data["user_count"] = this.userCount;

    data["connect_music"] = this.connectMusic;

    data["type"] = this.type;

    if (this.coverItem != null) {

      data["cover_item"] = this.coverItem.toJson();

    }

    return data;

  }

}

 

class CoverItem {

  String uri;

  List<String> urlList;

  CoverItem({this.uri, this.urlList});

  CoverItem.formJson(Map<String, dynamic> json) {

    this.uri = json["uri"] as String;

    this.urlList = json["url_list"].cast<String>();

  }

 

  Map<String, dynamic> toJson() {

    final Map<String, dynamic> data = new Map<String, dynamic>();

    data["url_list"] = this.urlList;

    data["uri"] = this.uri;

    return data;

  }

}

//End ChaList

 

//ShareInfo

class ShareInfo {

  String shareweibodesc;

  String sharedesc;

  String sharetitle;

  ShareInfo({this.shareweibodesc, this.sharedesc, this.sharetitle});

  ShareInfo.fromJson(Map<String, dynamic> json) {

    this.shareweibodesc = json["share_weibo_desc"];

    this.sharedesc = json["share_desc"];

    this.sharetitle = json["share_title"];

  }

  Map<String, dynamic> toJson() {

    final Map<String, dynamic> data = new Map<String, dynamic>();

    data["share_weibo_desc"] = this.shareweibodesc;

    data["share_desc"] = this.sharedesc;

    data["share_title"] = this.sharetitle;

    return data;

  }

}

//End ShareInfo

 

//RiskInfos

class RiskInfos {

  bool warn;

  int type;

  String content;

  RiskInfos({this.warn, this.type, this.content});

  RiskInfos.fromJson(Map<String, dynamic> json) {

    this.warn = json["warn"] as bool;

    this.type = json["type"];

    this.content = json["content"];

  }

  Map<String, dynamic> toJson() {

    final Map<String, dynamic> data = new Map<String, dynamic>();

    data["warn"] = this.warn;

    data["type"] = this.type;

    data["content"] = this.content;

    return data;

  }

}

//End RiskInfos

 

//Statistics

class Statistics {

  String awemeId;

  int commentCount;

  int diggCount;

  Statistics.fromJson(Map<String, dynamic> json) {

    this.awemeId = json["aweme_id"];

    this.commentCount = json["comment_count"];

    this.diggCount = json["digg_count"];

  }

  Map<String, dynamic> toJson() {

    final Map<String, dynamic> data = new Map<String, dynamic>();

    data["aweme_id"] = this.awemeId;

    data["comment_count"] = this.commentCount;

    data["digg_count"] = this.diggCount;

    return data;

  }

}

//End Statistics

 

//Music

class Music {

  CoverLarge coverlarge;

  CoverMedium covermedium;

  int duration;

  int status;

  String mid;

  String title;

  String author;

  PlayUrl playurl;

  String position;

  int id;

  CoverHd coverhd;

  CoverThumb coverthumb;

 

  Music.fromJson(Map<String, dynamic> json) {

    this.coverlarge = json["cover_large"] != null

        ? new CoverLarge.formJson(json["cover_large"])

        : null;

    this.covermedium = json["cover_medium"] != null

        ? new CoverMedium.formJson(json["cover_medium"])

        : null;

    this.duration = json["duration"];

    this.status = json["status"];

    this.mid = json["mid"];

    this.title = json["title"];

    this.author = json["author"];

    this.playurl = json["play_url"] != null

        ? new PlayUrl.formJson(json["play_url"])

        : null;

    this.position = json["position"];

    this.id = json["id"];

    this.coverhd = json["cover_hd"] != null

        ? new CoverHd.formJson(json["cover_hd"])

        : null;

    this.coverthumb = json["cover_thumb"] != null

        ? new CoverThumb.formJson(json["cover_thumb"])

        : null;

  }

 

  Map<String, dynamic> toJson() {

    final Map<String, dynamic> data = new Map<String, dynamic>();

    if (this.coverlarge != null) {

      data["cover_large"] = this.coverlarge.toJson();

    }

    if (this.covermedium != null) {

      data["cover_medium"] = this.covermedium.toJson();

    }

    data["duration"] = this.duration;

    data["status"] = this.status;

    data["mid"] = this.mid;

    data["title"] = this.title;

    data["author"] = this.author;

    if (this.playurl != null) {

      data["play_url"] = this.playurl.toJson();

    }

    data["position"] = this.position;

    data["id"] = this.id;

    if (this.coverhd != null) {

      data["cover_hd"] = this.coverhd.toJson();

    }

    if (this.coverthumb != null) {

      data["cover_thumb"] = this.coverthumb.toJson();

    }

    return data;

  }

}

 

class CoverLarge {

  String uri;

  List<String> urlList;

  CoverLarge({this.uri, this.urlList});

  CoverLarge.formJson(Map<String, dynamic> json) {

    this.uri = json["uri"] as String;

    this.urlList = json["url_list"].cast<String>();

  }

 

  Map<String, dynamic> toJson() {

    final Map<String, dynamic> data = new Map<String, dynamic>();

    data["url_list"] = this.urlList;

    data["uri"] = this.uri;

    return data;

  }

}

 

class CoverMedium {

  String uri;

  List<String> urlList;

  CoverMedium({this.uri, this.urlList});

  CoverMedium.formJson(Map<String, dynamic> json) {

    this.uri = json["uri"] as String;

    this.urlList = json["url_list"].cast<String>();

  }

 

  Map<String, dynamic> toJson() {

    final Map<String, dynamic> data = new Map<String, dynamic>();

    data["url_list"] = this.urlList;

    data["uri"] = this.uri;

    return data;

  }

}

 

class PlayUrl {

  String uri;

  List<String> urlList;

  PlayUrl({this.uri, this.urlList});

  PlayUrl.formJson(Map<String, dynamic> json) {

    this.uri = json["uri"] as String;

    this.urlList = json["url_list"].cast<String>();

  }

 

  Map<String, dynamic> toJson() {

    final Map<String, dynamic> data = new Map<String, dynamic>();

    data["url_list"] = this.urlList;

    data["uri"] = this.uri;

    return data;

  }

}

 

class CoverHd {

  String uri;

  List<String> urlList;

  CoverHd({this.uri, this.urlList});

  CoverHd.formJson(Map<String, dynamic> json) {

    this.uri = json["uri"] as String;

    this.urlList = json["url_list"].cast<String>();

  }

 

  Map<String, dynamic> toJson() {

    final Map<String, dynamic> data = new Map<String, dynamic>();

    data["url_list"] = this.urlList;

    data["uri"] = this.uri;

    return data;

  }

}

 

class CoverThumb {

  String uri;

  List<String> urlList;

  CoverThumb({this.uri, this.urlList});

  CoverThumb.formJson(Map<String, dynamic> json) {

    this.uri = json["uri"] as String;

    this.urlList = json["url_list"].cast<String>();

  }

 

  Map<String, dynamic> toJson() {

    final Map<String, dynamic> data = new Map<String, dynamic>();

    data["url_list"] = this.urlList;

    data["uri"] = this.uri;

    return data;

  }

}

//End Music

 

//Video

class Video {

  int width;

  OriginCover origincover;

  String ratio;

  bool haswatermark;

  String bitrate;

  PlayAddr playaddr;

  Cover cover;

  int duration;

  String vid;

  int islongvideo;

  int height;

  DynamicCover dynamiccover;

  Video(

      {this.width,

      this.origincover,

      this.ratio,

      this.haswatermark,

      this.bitrate,

      this.playaddr,

      this.cover,

      this.duration,

      this.vid,

      this.islongvideo,

      this.height,

      this.dynamiccover});

  Video.fromJson(Map<String, dynamic> json) {

    this.width = json["width"];

    this.origincover = json["origin_cover"] != null

        ? new OriginCover.formJson(json["origin_cover"])

        : null;

    this.ratio = json["ratio"];

    this.haswatermark = json["has_watermark"];

    this.bitrate = json["bit_rate"];

    this.playaddr = json["play_addr"] != null

        ? new PlayAddr.formJson(json["play_addr"])

        : null;

    this.cover =

        json["cover"] != null ? new Cover.formJson(json["cover"]) : null;

    this.duration = json["duration"];

    this.vid = json["vid"];

    this.islongvideo = json["is_long_video"];

    this.height = json["height"];

    this.dynamiccover = json["dynamic_cover"] != null

        ? new DynamicCover.formJson(json["dynamic_cover"])

        : null;

  }

 

  Map<String, dynamic> toJson() {

    final Map<String, dynamic> data = new Map<String, dynamic>();

    data["width"] = this.width;

    if (this.origincover != null) {

      data["origin_cover"] = this.origincover.toJson();

    }

    data["ratio"] = this.ratio;

    data["has_watermark"] = this.haswatermark;

    data["bit_rate"] = this.bitrate;

    if (this.playaddr != null) {

      data["play_addr"] = this.playaddr.toJson();

    }

    if (this.cover != null) {

      data["cover"] = this.cover.toJson();

    }

    data["duration"] = this.duration;

    data["vid"] = this.vid;

    data["is_long_video"] = this.islongvideo;

    data["height"] = this.height;

    if (this.dynamiccover != null) {

      data["dynamic_cover"] = this.dynamiccover.toJson();

    }

    return data;

  }

}

 

class OriginCover {

  String uri;

  List<String> urlList;

  OriginCover({this.uri, this.urlList});

  OriginCover.formJson(Map<String, dynamic> json) {

    this.uri = json["uri"] as String;

    this.urlList = json["url_list"].cast<String>();

  }

 

  Map<String, dynamic> toJson() {

    final Map<String, dynamic> data = new Map<String, dynamic>();

    data["url_list"] = this.urlList;

    data["uri"] = this.uri;

    return data;

  }

}

 

class PlayAddr {

  String uri;

  List<String> urlList;

  PlayAddr({this.uri, this.urlList});

  PlayAddr.formJson(Map<String, dynamic> json) {

    this.uri = json["uri"] as String;

    this.urlList = json["url_list"].cast<String>();

  }

 

  Map<String, dynamic> toJson() {

    final Map<String, dynamic> data = new Map<String, dynamic>();

    data["url_list"] = this.urlList;

    data["uri"] = this.uri;

    return data;

  }

}

 

class Cover {

  String uri;

  List<String> urlList;

  Cover({this.uri, this.urlList});

  Cover.formJson(Map<String, dynamic> json) {

    this.uri = json["uri"] as String;

    this.urlList = json["url_list"].cast<String>();

  }

 

  Map<String, dynamic> toJson() {

    final Map<String, dynamic> data = new Map<String, dynamic>();

    data["url_list"] = this.urlList;

    data["uri"] = this.uri;

    return data;

  }

}

 

class DynamicCover {

  String uri;

  List<String> urlList;

  DynamicCover({this.uri, this.urlList});

  DynamicCover.formJson(Map<String, dynamic> json) {

    this.uri = json["uri"] as String;

    this.urlList = json["url_list"].cast<String>();

  }

 

  Map<String, dynamic> toJson() {

    final Map<String, dynamic> data = new Map<String, dynamic>();

    data["url_list"] = this.urlList;

    data["uri"] = this.uri;

    return data;

  }

}

 

//End Video

  

取抖音无水印视频,这里才是关键

通过这个api地址 https://aweme.snssdk.com/aweme/v1/play/?video_id={}&ratio=720p&line=0&media_type=4&vr_type=0&improve_bitrate=0&is_play_url=1&h265=1&adapt720=1 可以成功的拿到无水印视频,

此方法仅用于学习研究目的,不得从事违法活动哈,  否则与本作者无关.

具体实现代码如下

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

import 'dart:io';

 

import 'package:flutter/cupertino.dart';

import 'package:http/http.dart' as http;

import 'package:dio/dio.dart';

 

class RequestController {

  //static String host = "https://www.tiktok.com/";

  static String host = "https://creator.douyin.com";

  String url = host + "/aweme/v1/creator/data/billboard/?billboard_type=4";

 

  String video =

      "https://www.iesdouyin.com/web/api/v2/aweme/iteminfo/?item_ids=";

 

  String player = "https://aweme.snssdk.com/aweme/v1/play/?video_id=";

 

  Future<String> getVideos() async {

    try {

      var response = await http.get(url);

      return response.body;

    } catch (e) {

      return e.toString();

    }

  }

 

  //获取无水印的视频

  Future<String> getRedirects(String videoid) async {

    try {

      var response = await new Dio().get(

        player +

            videoid +

            "&ratio=720p&line=0&media_type=4&vr_type=0&improve_bitrate=0&is_play_url=1&h265=1&adapt720=1",

        options: Options(

            headers: headers,

            contentType: "text/html; charset=utf-8",

            followRedirects: false,

            validateStatus: (status) {

              return status < 500;

            }),

      );

      if (response.statusCode == 302) {

        return response.data.toString().split('"')[1];

      }

      return '';

    } catch (ex) {

      return '';

    }

  }

 

  Future<String> getCookie() async {

    try {

      var response = await http.get(host + "/share/item/");

      return response.headers["set-cookie"];

    } catch (e) {

      return "error";

    }

  }

 

  var headers = {

    "user-agent":

        "Mozilla/5.0 (iPhone; CPU iPhone OS 11_0 like Mac OS X) AppleWebKit/604.1.38 (KHTML, like Gecko) Version/11.0 Mobile/15A372 Safari/604.1"

  };

}

 

剩下就是改造TrendingScreen里获取视频的方法 

1

2

3

4

5

6

7

8

9

10

11

12

13

14

15

16

17

18

19

20

21

22

23

getTrending() async {

  //var cookies = await api.getCookie();

  //api.setCookie(cookies);

  try {

    var response = await http.get(

      api.url,

      headers: api.headers,

    );

    Douyin tiktok = Douyin.fromJson(jsonDecode(response.body));

    tiktok.billboardData.forEach(

      (item) {

        setState(() {

          getVideos(item);

        });

      },

    );

  } catch (ex) {

    SimpleDialog(

      title: Text('Hot videos list is empty'),

    );

    print(ex);

  }

}

  

把获取到的无水印视频加载到view层

1

2

3

4

5

6

7

8

9

10

11

12

13

14

15

16

17

18

19

20

21

22

23

getVideos(BillboardData v) async {

    try {

      var url = v.link.split("/")[5];

      var response = await http.get(

        api.video + url + "&dytk",

        headers: api.headers,

      );

      VideoData videoData = VideoData.fromJson(jsonDecode(response.body));

      //获取无水印的视频地址

      api.getRedirects(videoData.itemList[0].video.playaddr.uri).then((url) => {

            print( Uri.decodeFull(url)),

            if (url != '')

              {

                videos.add(VideoItem(

                  data: videoData,

                  videourl: url,

                ))

              }

          });

    } catch (ex) {

      print(ex);

    }

  }

 

最后,就是绑定视频数据啦,大功告成!

1

2

3

4

5

6

7

8

9

10

11

12

13

14

15

16

17

18

19

20

21

22

23

24

Widget build(BuildContext context) {

  return Scaffold(

    body: Stack(

      children: <Widget>[

        DouyinVideoPlayer(

          url: videourl,

        ),

        title(),

        VideoDescription(

          description: data.itemList[0].textExtras[0].hashtagName,

          musicName: data.itemList[0].music.title,

          authorName: data.itemList[0].music.author,

          userName: data.itemList[0].author.nickname,

        ),

        ActionsToolbar(

          comments: data.itemList[0].statistics.commentCount.toString(),

          userImg: data.itemList[0].author.avatarMedium.urlList[0],

          favorite: data.itemList[0].statistics.diggCount,

          coverImg: data.itemList[0].music.covermedium.urlList[0],

        ),

      ],

    ),

  );

}

 

结语

写到这里,手撸一个抖音app完成了基本功能,一些数据转化比喻点赞数还是原始int数据,需要转化成K M 等,后续待完善,毕竟花了一天时间来写这个app纯粹为了兴趣. 本人也极喜欢玩抖音.

另外因为写得匆忙,只测试了少量数据,json返回的数据有些为null ,也不知道具体是啥类型,这个待完善。 各位博友们觉得感兴趣的点个赞哈.  支持支持!

 

顺便说一下,各位支持的话点赞关注我继续完善剩余的部分,有需要学习了解的可以联系我,代码上传到github ,地址:https://github.com/WangCharlie/douyin,到这里就点个star, 谢谢.

 

开源Flutter3.19仿抖音实战(开发自己的抖音APP) 开源Flutter3.19仿抖音实战(开发自己的抖音APP),最近有空的时候用flutter3做了一个抖音的项目,方便大家学习,提供源码,有操作思路。原创开发基于flutter3.19.5+dart3.3.3+getx等技术开发仿抖音app实战项目。实现了类似抖音整屏丝滑式上下滑动视频、左右滑动切换页面模块 阅读详情

相关推荐

【Flutter应用】Flutter精仿抖音开源

总而言之,Android开发行业变化太快,作为技术人员就要保持终生学习的态度,让学习力成为核心竞争力,所谓“活到老学到老”只有不断的学习,不断的提升自己,才能跟紧行业的步伐,才能不被时代所淘汰。在这里我分享一份自己收录整理上述技术体系图相关的几十套腾讯、头条、阿里、美团等公司19年的面试题,把技术点整理成了视频和PDF(实际上比预期多花了不少精力),包含知识脉络 + 诸多细节,由于篇幅有限,这里以图片的形式给大家展示一部分。需要的朋友可以私信我【资料】或者点这里免费领取。

m0_60567796的博客 1354

偷个懒,公号抠腚早报80%自动化——5.意思意思撸个APP收下尾

0x1 简述 终于来到本系列的最后一节咯,本节撸个早报APP来调用下上节编写的接口~ 写完这个系列都有种自己是「全栈」工程师的错觉了~ 0x2 产品原型设计环节 APP的功能虽说比较简单,但是竟然说了全栈,意思意思用Axure RP 8 大概的制作下原型(装波逼), 涉及到五个页面,依次是:「今日新闻」,「新闻筛选」,「早报」,「新闻编辑」和「新闻详情」, 具体页面逻辑与交互详情如下: ...

weixin_34130389的博客 765

【源码版】flutter3.19仿抖音实战企业级APP应用

源码版flutter3.19仿抖音实战企业级APP应用,全新Flutter原创基于flutter3.19.5+dart3.3.3+getx等技术开发仿抖音app实战项目。实现了类似抖音整屏丝滑式上下滑动视频、左右滑动切换页面模块,商城、购物车、支付功能等模块。同时接入了友盟SDK统计数据。

flutter开发app的博客 1360

VML 魅力初现--美少女图(vml可以这样强大?)

Vml应用实例――vml少女图: 源码:       content-type? xmlns:v="urn:schemas-microsoft-com:vml">                     VML 画图示例--美少女                     H3 { MARGIN-LEFT: 5%; COLOR: #ffdead; MARG

crazy_rain的专栏 2万+

【Flutter应用】Flutter精仿抖音开源(1)

对于程序员来说,要学习的知识内容、技术有太多太多,要想不被环境淘汰就只有不断提升自己,从来都是我们去适应环境,而不是环境来适应我们!当你有了学习线路,学习哪些内容,也知道以后的路怎么走了,理论看多了总要实践的最后,互联网不存在所谓的寒冬,只是你没有努力罢了!《互联网大厂面试真题解析、进阶开发核心学习笔记、全套讲解视频、实战项目源码讲义》点击传送门即可获取!阶开发核心学习笔记、全套讲解视频、实战项目源码讲义》点击传送门即可获取!**

2401_84149505的博客 1240

原创开发Flutter3.19仿抖音实战,学不会我去刷厕所

最近有空的时候用flutter3做了一个抖音的项目,方便大家学习,提供源码,有操作思路。原创开发基于flutter3.19.5+dart3.3.3+getx等技术开发仿抖音app实战项目。实现了类似抖音整屏丝滑式上下滑动视频、左右滑动切换页面模块,包含商城、购物车、支付功能等模块。 同时接入了友盟SDK统计数据,用户下载安装,活跃量,次日留存等等。

flutter开发app的博客 2416

php oef,webshell/php/pas/pas.php at e9b09f671b36e954110886a291808c1e921f3c0f · tennc/webshell · GitH...

nrjFFHG8nJ/PCbYaLRGtQrnqa/GFo2Ze2M2ANGO/HsLXxi8rEQBh3AvF/Fb96KIRTcEdNq3qyTIGloRbnPh1eZjdYeOYcYc2wTQxGIOgB9TeWZjEXT8yuDJPAIfUbPXpHl1kdeqE9RgH+fRZxLUpI7FRzDnSu7R8pXcHfoO0qfFe1whsnvj72Pas8t6vzVpU/5OEcjjz...

weixin_33438788的博客 2万+

华为HCIP-001:PPPOE配置模拟实验

华为HCIP01:PPPOE配置模拟实验 实验环境 pppoe-client上面的接口信息 [AR1]dis ip interface brief *down: administratively down ^down: standby (l): loopback (s): spoofing The number of interface that is UP in Physical is 2 The number of interface that is DOWN in Physical is 1 Th

兔子乖乖 1802

Opencv加QOpenGLWidget 手撸视频播放器,实现自己的看片神器

Opencv加QOpenGLWidget 手撸视频播放器,实现自己的看片神器 开发环境: VS2017+QtQt5.9.3+Opencv3.4.3 废话不多说,直接上代码 MyXVideo.h #pragma once #include <QOpenGLWidget> #include<QTimerEvent> #include <QPaintEvent> #include "opencv2/core.hpp" #include "opencv2/imgcodecs.hp

weixin_42309565的博客 23万+

Open3D:读写点云与可视化(C++)

迭代 //Open3D #include "Open3D/Open3D.h" //Eigen #include "Eigen/Dense" void testOpen3D::xjGetInformation(const QString &pcPath) { /* read PC */ auto cloud_ptr = std::make_shared<open3d::geometry::PointCloud>(); if (!open3d::io::ReadPointCl

累了就要打游戏 1万+

使用 Restart Extension 继续解决 Ansys 机械分析

您正在执行复杂或大型的多步有限元分析,突然,您意识到经过数小时的求解后,您需要添加或修改边界条件。在本篇博文中,您将了解如何利用 Ansys Mechanical 中的 RestartAnalysis 扩展来扩展多步分析,方法是合并额外的时间步骤和边界条件,而不会丢失已计算的结果。幸运的是,在 Mechanical 中,有一种直接的方法可以实现这一点,而不会损害您之前的结果,而这在添加新边界条件和使用重新启动点时经常发生。得到的结果符合预期,您可以看到施加的力产生的这两个部分之间的相对位移。

jkh920184196的博客 1233

signature=6787c6893a06841ec3a6259702047ce8,certbot/certbot-auto at d62d853ea4c03676f1a10248d01b91aa6...

#!/bin/sh## Download and run the latest release version of the Certbot client.## NOTE: THIS SCRIPT IS AUTO-GENERATED AND SELF-UPDATING## IF YOU WANT TO EDIT IT LOCALLY, *ALWAYS* RUN YOUR COPY WITH THE...

weixin_33274728的博客 1万+

Fortify SCA 24.4 最新可用版

Fortify Static Code Analyzer(Fortify SCA) 是一款由 Micro Focus(原惠普旗下)开发的 静态应用安全测试(SAST)工具,专注于在软件开发早期通过分析源代码或编译后的二进制文件,识别潜在的安全漏洞、代码缺陷和合规性问题。以下是其核心特点与功能:核心功能 静态代码分析 在不运行程序的情况下,通过语法、语义分析检测代码中的安全漏洞(如 SQL 注入、跨站脚本(XSS)、缓冲区溢出等)和代码质量问题。

棉花糖的博客 3185

第四篇-用Flutter手撸一个抖音国内版,看看有多炫

前言 这次对布局进行优化,主要包含了首页tabview pageview 以及添加几个按钮的操作过程.主要使用到stack层叠布局,tabpview和pageview,tabview两个页面,一个关注,一个推荐,左右切换,pageview被包含在tabview里面. 布局优化 抖音的顶部appbar 是悬浮层叠展示,而flutter的层叠组件是stack, 因此最外面采用stack, 其次中间是tabview,分别是关注和推荐两个选项卡,关注在没有登录的时候会弹出一个提示需要认证登录的页面,这里加

wclhack的专栏 1706

工业级AI智能体落地:OpenClaw+ONNX Runtime全栈部署,端到端延迟压至12ms

OpenClaw+ONNX的组合为工业AI智能体的落地提供了一套完整、高效、安全的解决方案。它解决了传统云端智能体在工业场景中面临的延迟、安全和稳定性问题,实现了本地数据闭环和毫秒级响应。未来,随着边缘计算和轻量级大模型技术的发展,工业AI智能体将变得更加智能和强大。我们可以预见,在不久的将来,工厂里的每一台设备都将拥有自己的AI智能体,它们能够自主感知、自主决策、自主执行,真正实现工业生产的全自动化和智能化。👉 点击我的头像进入主页,关注专栏第一时间收到更新提醒,有问题评论区交流,看到都会回。

2601_94871597的博客 736

YOLOv12涨点改进 | Neck特征融合改进篇 | YOLOv12引 入 BiFPN双向特征金字塔网络,显著提升了多尺度特征融合效率,目标检测、图像分割、图像分类,助力高效涨点

本文提出了一种基于BiFPN特征融合模块优化的YOLOv12目标检测方法。BiFPN通过引入可学习的权重实现高效的多尺度特征融合,结合双向连接机制提升特征提取能力。实验表明,该方法在COCO数据集上实现了优异的检测性能。文章详细介绍了BiFPN模块的原理、核心代码实现,以及如何将其集成到YOLOv12框架中,包括模块添加、配置文件修改等具体步骤。这种改进方案具有参数少、计算量小等优势,为高效目标检测提供了一种新的技术路径。

Ai缝合怪、助力小伙伴高效发论文 393

用Flutter手撸一个抖音国际版 看看有多炫

肉眼品世界导读:本文为启明星技术架构师社群作者投稿,字节跳动选择了flutter作为混合开发的语言,Flutter “一出生”就以“UI 漂亮、像素级可控、性能流畅、可媲美原生性能”等...

weixin_45727359的博客 1518

100天精通Python(爬虫篇)——第118天:selenium自动化操作浏览器基础大总结

一、Selenium框架环境搭建 1. 下载模块 2. 安装浏览器驱动WebDriver 二、基础操作 1. 打开浏览器 2. 无界面模式 3. 元素定位 4. 元素操作 5. 前进后退 6. 执行js 7.页面等待 隐式等待(常用) 显式等待(了解) 三、进阶操作 1. 使用代理ip 2. 修改请求头 3. 隐藏指纹特征.....................

努力让自己发光,对的人才能迎着光而来 10万+

第三篇-用Flutter手撸一个抖音国内版,看看有多炫

前言 前一篇已经开发了大部分框架,包含视频上下滑动播放,这次将上次未完成的数据显示友好显示,以及底部音乐走马灯特效,另外优化了加载数据的bug,在dart语言里 & 会自动变成&amp; 也不知道这个bug啥时候修复哈. 本系列会持续更新,将各个模块及功能持续完善. 修复Dart语言 URL显示错误. 暂时只能这样替换了 1 url = url.replaceAll('&','&'), 1 2...

wclhack的专栏 672

Allegro如何导出和导入器件模型Signal_Model操作指导

在用Allegro做PCB设计的时候,通常需要给器件加上Signal_Model,在做等长的时候用到的非常频繁。如下图,需要把R7002的Signal_Model导入到另外一块没有给R7002加好模型的BRD中。Allegro除了可以给器件添加模型,还支持从一块加好模型的BRD导入到另外一块BRD中,如何导出和导入器件模型Signal_Model操作指导。

weixin_54787054的博客 1897

efm32 入门.pdf

本PDF共计22页,介绍的是操作驱动API,下载与否自己斟酌。

flutter json转对象_用Flutter手撸一个抖音国际版 看看有多炫

肉眼品世界导读:本文为启明星技术架构师社群作者投稿,字节跳动选择了flutter作为混合开发的语言,Flutter “一出生”就以“UI 漂亮、像素级可控、性能流畅、可媲美原生性能”等特点吸引广大开发者的眼球,自渲染引擎甚至具备开发游戏的能力。更多优质内容请关注微信公众号“肉眼品世界”(ID:find_world_fine),深度价值体系传递技术日新月异,都是站在巨人肩膀上的思考,前端...

weixin_42508241的博客 266

visio安装包

Microsoft Office Visio 是一款专业的制图软件。利用经过更新的形状、协作工具和数据链接图表,创建专业级图表以简化复杂的信息。

第五篇- 抖音的强大对手来了,用Flutter手撸一个抖音国际版,看看有多炫

前言 由于中间几个月项目天天加班,导致没没时间更新,最近一段时间对前端进行了重构,加了很多页面,如登录、注册、关注、个人中心等,目前写这个纯属业余个人爱好,所以断断续续的继续在做… 前端地址:https://www.pgyer.com/dtok 后端服务器地址:http://47.95.209.198:8181/ 注释:由于本人的apple id无法打包ios、所以暂时只打包的android版本,ios版本正在解决账号问题 效果如下: 架构更新 之前技术采用flutter做的前端,后端api则对接的是

wclhack的专栏 3163

汉书HE8,好用的汇编代码编写工具

汉书HE8,好用的汇编代码编写工具早期编写汇编代码所用功能强大

jdk-8u144-linux-x64.tar.gz.

该资源为搭建Linux系统Java环境用,资源的后缀为tar.gz格式的压缩包

flutter图标按钮_第四篇-用Flutter手撸一个抖音国内版,看看有多炫

前言这次对布局进行优化,主要包含了首页tabview pageview 以及添加几个按钮的操作过程.主要使用到stack层叠布局,tabpview和pageview,tabview两个页面,一个关注,一个推荐,左右切换,pageview被包含在tabview里面.布局优化抖音的顶部appbar 是悬浮层叠展示,而flutter的层叠组件是stack, 因此最外面采用stack, 其次中间是tabv...

weixin_42508557的博客 352

抖音的强大对手来了,用Flutter手撸一个抖音国际版,看看有多炫

前言由于中间几个月项目天天加班,导致没没时间更新,最近一段时间对前端进行了重构,加了很多页面,如登录、注册、关注、个人中心等,目前写这个纯属业余个人爱好,所以断断续续的继续在做.........

weixin_45727359的博客 512

Flutter 用 PageView 仿抖音滑动页面,太炫酷了吧

接入字节跳动穿山甲广告//穿山甲广告 Android appid 必填//穿山甲广告 ios appid 必填//使用TextureView控件播放视频,默认为SurfaceView,当有SurfaceView冲突的场景,可以使用TextureView 选填//appname 必填//是否允许sdk展示通知栏提示 选填//是否在锁屏场景支持展示广告落地页 选填//测试阶段打开,可以通过日志排查问题,上线时去除该调用 选太难//是否支持多进程,true支持 选填]);

it7899的博客 1531
上一篇: 使用Flutter开发的抖音国际版
下一篇: 第三篇-用Flutter手撸一个抖音国内版,看看有多炫
wclhack
博客等级 码龄18年 15粉丝 11原创
评论
成就一亿技术人!
拼手气红包6.0元
还能输入1000个字符
 
 条评论被折叠 查看
添加红包

请填写红包祝福语或标题

个

红包个数最小为10个

元

红包金额最低5元

当前余额3.43元 前往充值 >
需支付:10.00元
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付元
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值