简介
free_wallpaper是一款基于flutter的免费Android壁纸应用
项目明细
开发环境:
android studio 3.5
Flutter 1.12.13+hotfix.7 • channel stable
Framework • revision 9f5ff2306b
Engine • revision a67792536c
Tools • Dart 2.7.0
主要功能
1.分端浏览
2.筛选功能
3.搜索功能
4.搜索历史记录
5.下载壁纸和设置壁纸
6.下载管理
(更多功能还在陆续填坑中...)
项目特点
本项目采用Serverless模式,使用dio网络请求库和jsoup获取数据。使用百度图片接口实现搜索功能;使用CachedNetworkImage加载网络图片;使用Android插件的形式实现了部分Android原生功能,如设置壁纸和更新MediaStore等。
部分代码
1.WallpaperPlugin(修改自wallpaper插件,解决了dio库版本不兼容的问题,添加了设置本地图片的功能)
import android.Manifest
import android.annotation.TargetApi
import android.app.Activity
import android.app.WallpaperManager
import android.content.ActivityNotFoundException
import android.content.ContentValues
import android.content.Context
import android.content.Intent
import android.content.pm.PackageManager
import android.graphics.BitmapFactory
import android.net.Uri
import android.os.Build
import android.provider.MediaStore
import io.flutter.Log
import io.flutter.embedding.android.FlutterActivity
import io.flutter.embedding.engine.plugins.FlutterPlugin
import io.flutter.plugin.common.MethodCall
import io.flutter.plugin.common.MethodChannel
import io.flutter.plugin.common.MethodChannel.MethodCallHandler
import java.io.File
import java.io.IOException
/** WallpaperPlugin */
class WallpaperPlugin constructor(var mContext: Context): FlutterActivity(), FlutterPlugin, MethodCallHandler {
private var id = 0
private var res = ""
private var channel: MethodChannel?=null
@TargetApi(Build.VERSION_CODES.FROYO)
private fun setWallpaper(i: Int, imagePath: String): String {
id = i
val wallpaperManager = WallpaperManager.getInstance(mContext)
val file = File(imagePath)
// set bitmap to wallpaper
val bitmap = BitmapFactory.decodeFile(file.absolutePath)
if (id == 1) {
try {
res = if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.N) {
wallpaperManager.setBitmap(bitmap, null, true, WallpaperManager.FLAG_SYSTEM)
"Home Screen Set Successfully"
} else {
"To Set Home Screen Requires Api Level 24"
}
} catch (ex: IOException) {
ex.printStackTrace()
}
} else if (id == 2) try {
res = if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.N) {
wallpaperManager.setBitmap(bitmap, null, true, WallpaperManager.FLAG_LOCK)
"Lock Screen Set Successfully"
} else {
"To Set Lock Screen Requires Api Level 24"
}
} catch (e: IOException) {
res = e.toString()
e.printStackTrace()
} else if (id == 3) {
try {
wallpaperManager.setBitmap(bitmap)
res = "Home And Lock Screen Set Successfully"
} catch (e: IOException) {
res = e.toString()
e.printStackTrace()
}
} else if (id == 4) {
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.M) {
if (activity.checkSelfPermission(Manifest.permission.READ_EXTERNAL_STORAGE)
!= PackageManager.PERMISSION_GRANTED &&
activity.checkSelfPermission(Manifest.permission.WRITE_EXTERNAL_STORAGE)
!= PackageManager.PERMISSION_GRANTED) {
activity.requestPermissions(arrayOf(Manifest.permission.READ_EXTERNAL_STORAGE,
Manifest.permission.WRITE_EXTERNAL_STORAGE), 1)
} else {
Uri.fromFile(file)
val contentURI = getImageContentUri(this, file)
val intent = Intent(wallpaperManager.getCropAndSetWallpaperIntent(contentURI))
val mime = "image/*"
intent.setDataAndType(contentURI, mime)
try {
startActivityForResult(intent, 2)
} catch (e: ActivityNotFoundException) { //handle error
res = "Error To Set Wallpaer"
}
}
}
}
return res
}
override fun onActivityResult(requestCode: Int, resultCode: Int, data: Intent) {
Log.d("Tag", "resultcode=" + resultCode + "requestcode=" + requestCode)
res = when (resultCode) {
Activity.RESULT_OK -> {
"System Screen Set Successfully"
}
Activity.RESULT_CANCELED -> {
"setting Wallpaper Cancelled"
}
else -> {
"Something Went Wrong"
}
}
super.onActivityResult(requestCode, resultCode, data)
}
override fun onMethodCall(call: MethodCall, result: MethodChannel.Result) {
when (call.method) {
"getPlatformVersion" -> result.success("" + Build.VERSION.RELEASE)
"HomeScreen" -> result.success(setWallpaper(1, call.arguments as String))
"LockScreen" -> result.success(setWallpaper(2, call.arguments as String))
"Both" -> result.success(setWallpaper(3, call.arguments as String))
"SystemWallpaer" -> result.success(setWallpaper(4, call.arguments as String))
else -> result.notImplemented()
}
}
override fun onAttachedToEngine(binding: FlutterPlugin.FlutterPluginBinding) {
channel = MethodChannel(binding.binaryMessenger, "WallpaperPlugin")
channel!!.setMethodCallHandler(WallpaperPlugin(mContext))
}
override fun onDetachedFromEngine(binding: FlutterPlugin.FlutterPluginBinding) {
channel!!.setMethodCallHandler(null)
channel=null
}
companion object {
fun getImageContentUri(context: Context, imageFile: File): Uri? {
val filePath = imageFile.absolutePath
Log.d("Tag", filePath)
val cursor = context.contentResolver.query(
MediaStore.Images.Media.EXTERNAL_CONTENT_URI, arrayOf(MediaStore.Images.Media._ID),
MediaStore.Images.Media.DATA + "=? ", arrayOf(filePath), null)
return if (cursor != null && cursor.moveToFirst()) {
val id = cursor.getInt(cursor
.getColumnIndex(MediaStore.MediaColumns._ID))
val baseUri = Uri.parse("content://media/external/images/media")
Uri.withAppendedPath(baseUri, "" + id)
} else {
if (imageFile.exists()) {
val values = ContentValues()
values.put(MediaStore.Images.Media.DATA, filePath)
context.contentResolver.insert(
MediaStore.Images.Media.EXTERNAL_CONTENT_URI, values)
} else {
null
}
}
}
}
}
2.MediaStorePlugin(更新本机媒体库)
import android.content.Context
import android.content.Intent
import android.graphics.BitmapFactory
import android.net.Uri
import android.provider.MediaStore
import io.flutter.embedding.engine.plugins.FlutterPlugin
import io.flutter.plugin.common.MethodCall
import io.flutter.plugin.common.MethodChannel
import java.io.File
/**
* description:
* author:luoxingyuan
*/
class MediaStorePlugin constructor(var mContext: Context) : FlutterPlugin, MethodChannel.MethodCallHandler {
private var channel: MethodChannel?=null
override fun onAttachedToEngine(binding: FlutterPlugin.FlutterPluginBinding) {
channel = MethodChannel(binding.binaryMessenger, "MediaStorePlugin")
channel!!.setMethodCallHandler(MediaStorePlugin(mContext))
}
override fun onDetachedFromEngine(binding: FlutterPlugin.FlutterPluginBinding) {
channel!!.setMethodCallHandler(null)
channel=null
}
override fun onMethodCall(call: MethodCall, result: MethodChannel.Result) {
when (call.method) {
"refreshMediaStore" -> result.success(sendMediaBroadcast(call.arguments as String))
else -> result.notImplemented()
}
}
private fun sendMediaBroadcast(filePath: String) {
val file = File(filePath)
//通知相册更新
MediaStore.Images.Media.insertImage(mContext.contentResolver, BitmapFactory.decodeFile(file.absolutePath), file.name, null)
val intent = Intent(Intent.ACTION_MEDIA_SCANNER_SCAN_FILE)
val uri: Uri = Uri.fromFile(file)
intent.data = uri
mContext.sendBroadcast(intent)
}
}
3.PC端壁纸分类列表
import 'package:cached_network_image/cached_network_image.dart';
import 'package:flutter/material.dart';
import 'package:free_wallpaper/model/category_model.dart';
import 'package:free_wallpaper/net/address.dart';
import 'package:free_wallpaper/net/http_callback.dart';
import 'package:free_wallpaper/net/http_manager.dart';
import 'package:free_wallpaper/net/result_data.dart';
import 'package:free_wallpaper/utils/toast.dart';
import 'package:free_wallpaper/widget/error_placeholder.dart';
import 'package:free_wallpaper/widget/loading_dialog.dart';
import 'package:html/parser.dart' show parse;
import 'page_albums.dart';
/*
description:
author:59432
create_time:2020/1/22 12:59
*/
class CategoriesPage extends StatefulWidget {
@override
State createState() => CategoriesPageState();
}
class CategoriesPageState extends State {
var categories = List();
@override
void initState() {
// TODO: implement initState
super.initState();
_requestData(showLoading: true);
}
@override
Widget build(BuildContext context) {
return Scaffold(
body: RefreshIndicator(
color: Colors.pinkAccent,
backgroundColor: Colors.white,
child: Container(
margin: const EdgeInsets.only(left: 8.0, right: 8, top: 8),
child: GridView.count(
// Create a grid with 2 columns. If you change the scrollDirection to
// horizontal, this produces 2 rows.
crossAxisCount: 3,
crossAxisSpacing: 8.0,
mainAxisSpacing: 8,
// Generate 100 widgets that display their index in the List.
children: List.generate(categories.length, (index) {
return _buildItem(context, categories[index]);
}),
),
), onRefresh: _refreshData),
);
}
_requestData({showLoading = false}) {
HttpManager.getInstance(baseUrl: Address.MEI_ZHUO)
.getHtml("/zt/index.html", HttpCallback(
onStart: () {
if (showLoading) {
LoadingDialog.showProgress(context);
}
},
onSuccess: (ResultData data) {
if (showLoading) {
LoadingDialog.dismiss(context);
}
var doc = parse(data.data);
var aTags = doc.body
.getElementsByClassName("nr_zt w1180")
.first
.getElementsByTagName("a");
categories.clear();
aTags.forEach((a) {
var href = a.attributes["href"];
var src = a
.querySelector("img")
.attributes["src"];
var category = a
.querySelector("p")
.text;
categories.add(CategoryModel(name: category, href: href, src: src));
});
setState(() {
});
},
onError: (ResultData error) {
if (showLoading) {
LoadingDialog.dismiss(context);
}
ToastUtil.showToast(error.data);
}
));
}
Widget _buildItem(BuildContext context, CategoryModel category) {
return GestureDetector(
onTap: () => _onItemClick(category),
child: ClipOval(
child: Stack(
alignment: const Alignment(0.0, 1.0),
children: [
CachedNetworkImage(
imageUrl: category.src,
placeholder: (context, url) => Center(child: CircularProgressIndicator()),
errorWidget: (context, url, error) => ErrorPlaceHolder(),
fit: BoxFit.fill,
height: (MediaQuery
.of(context)
.size
.width) / 3,
),
Container( //分析 4
width: (MediaQuery
.of(context)
.size
.width) / 3,
decoration: BoxDecoration(
color: Colors.black45,
),
child: Text(
category.name,
textAlign: TextAlign.center,
style: TextStyle(
fontSize: 16.0,
color: Colors.white,
),
),
),
],
),
),
);
}
_onItemClick(CategoryModel category) {
Navigator.push(
context,
MaterialPageRoute(builder: (context) => AlbumsPage(category, false)),
);
}
Future _refreshData() async {
_requestData();
}
}
预览
项目地址
https://github.com/lxygithub/free_wallpaper