mirror of
https://gitee.com/houstudio/Cdroid.git
synced 2024-11-30 11:18:02 +08:00
add dropdownlistview
This commit is contained in:
parent
7a6ca7af18
commit
4b05691c3d
@ -1,18 +1,13 @@
|
||||
#include<theme.h>
|
||||
#include<expat.h>
|
||||
#include<cdtypes.h>
|
||||
#include<cdlog.h>
|
||||
#include<iostream>
|
||||
#include <theme.h>
|
||||
#include <expat.h>
|
||||
#include <cdtypes.h>
|
||||
#include <cdlog.h>
|
||||
#include <iostream>
|
||||
#include <iomanip>
|
||||
#include <sstream>
|
||||
#include <string.h>
|
||||
|
||||
|
||||
namespace cdroid{
|
||||
static void startElement(void *userData, const XML_Char *name, const XML_Char **atts){
|
||||
}
|
||||
static void endElement(void *userData, const XML_Char *name){
|
||||
}
|
||||
|
||||
Theme*Theme::mInst=nullptr;
|
||||
Theme&Theme::getInstance(){
|
||||
@ -21,29 +16,79 @@ Theme&Theme::getInstance(){
|
||||
return *mInst;
|
||||
}
|
||||
|
||||
int Theme::parseStyles(std::istream&stream){
|
||||
int done=0;
|
||||
typedef struct StyleData{
|
||||
std::map<const std::string,AttributeSet>*maps;
|
||||
AttributeSet*cur;
|
||||
std::string key;
|
||||
std::string value;
|
||||
}STYLEDATA;
|
||||
|
||||
static void startElement(void *userData, const XML_Char *name, const XML_Char **satts){
|
||||
AttributeSet atts(satts);
|
||||
STYLEDATA*sd=(STYLEDATA*)userData;
|
||||
std::map<const std::string,AttributeSet>*maps=sd->maps;
|
||||
|
||||
if(0==strcmp(name,"style")){
|
||||
const std::string stylename=atts.getString("name");
|
||||
const std::string parent=atts.getString("parent");
|
||||
auto it=maps->find(stylename);
|
||||
LOGD("<%s> parent=%s",stylename.c_str(),parent.c_str());
|
||||
if(it!=maps->end()){
|
||||
sd->cur=&it->second;
|
||||
}else{
|
||||
auto it2=maps->insert(std::make_pair<const std::string,AttributeSet>(stylename.c_str(),AttributeSet()));
|
||||
sd->cur=&(it2.first->second);
|
||||
if(!parent.empty())sd->cur->add("parent",parent);
|
||||
}
|
||||
}else if(0==strcmp(name,"item")){
|
||||
sd->key = atts.getString("name");
|
||||
sd->value=std::string();
|
||||
}
|
||||
}
|
||||
|
||||
static void CharacterHandler(void *userData,const XML_Char *s, int len){
|
||||
STYLEDATA*sd=(STYLEDATA*)userData;
|
||||
sd->value+=std::string(s,len);
|
||||
}
|
||||
|
||||
static void endElement(void *userData, const XML_Char *name){
|
||||
STYLEDATA*sd=(STYLEDATA*)userData;
|
||||
std::map<const std::string,AttributeSet>*maps=sd->maps;
|
||||
if(0==strcmp(name,"style")){
|
||||
sd->cur=nullptr;
|
||||
}else if(0==strcmp(name,"item")){
|
||||
LOGV("\t%s=%s",sd->key.c_str(),sd->value.c_str());
|
||||
sd->cur->add(sd->key,sd->value);
|
||||
}
|
||||
}
|
||||
|
||||
int Theme::loadStyles(std::istream&stream){
|
||||
int len;
|
||||
char buf[256];
|
||||
|
||||
XML_Parser parser = XML_ParserCreate(NULL);
|
||||
XML_SetUserData(parser,nullptr);
|
||||
XML_Parser parser=XML_ParserCreate(nullptr);
|
||||
std::string curKey;
|
||||
std::string curValue;
|
||||
STYLEDATA sd={&mStyles,nullptr};
|
||||
XML_SetUserData(parser,&sd);
|
||||
XML_SetElementHandler(parser, startElement, endElement);
|
||||
//XML_SetCharacterDataHandler(parser,dataHandler);
|
||||
|
||||
XML_SetCharacterDataHandler(parser,CharacterHandler);
|
||||
do {
|
||||
int len=stream.readsome(buf,sizeof(buf));
|
||||
done=(len==0);
|
||||
if (XML_Parse(parser, buf,len,done) == XML_STATUS_ERROR) {
|
||||
const char*es=XML_ErrorString(XML_GetErrorCode(parser));
|
||||
LOGE("%s at line %ld",es, XML_GetCurrentLineNumber(parser));
|
||||
return 1;
|
||||
}
|
||||
} while(!done);
|
||||
stream.read(buf,sizeof(buf));
|
||||
len=stream.gcount();
|
||||
if (XML_Parse(parser, buf,len,len==0) == XML_STATUS_ERROR) {
|
||||
const char*es=XML_ErrorString(XML_GetErrorCode(parser));
|
||||
LOGE("%s at line %ld",es, XML_GetCurrentLineNumber(parser));
|
||||
XML_ParserFree(parser);
|
||||
return 0;
|
||||
}
|
||||
} while(len!=0);
|
||||
XML_ParserFree(parser);
|
||||
return 0;
|
||||
}
|
||||
|
||||
const AttributeSet Theme::getStyle(const std::string&name)const{
|
||||
auto it=mStyles.find(name);
|
||||
if(it!=mStyles.end())
|
||||
return it->second;
|
||||
return AttributeSet();
|
||||
}
|
||||
|
||||
|
@ -11,11 +11,11 @@ namespace cdroid{
|
||||
|
||||
class Theme{
|
||||
protected:
|
||||
std::map<std::string,AttributeSet>mStyles;
|
||||
std::map<const std::string,AttributeSet>mStyles;
|
||||
static Theme*mInst;
|
||||
public:
|
||||
static Theme&getInstance();
|
||||
int parseStyles(std::istream&s);
|
||||
int loadStyles(std::istream&s);
|
||||
const AttributeSet getStyle(const std::string&name)const;
|
||||
};
|
||||
|
||||
|
@ -1649,6 +1649,26 @@ bool AbsListView::canScrollDown() {
|
||||
return canScrollDown;
|
||||
}
|
||||
|
||||
void AbsListView::scrollListBy(int y){
|
||||
trackMotionScroll(-y,-y);
|
||||
}
|
||||
|
||||
bool AbsListView::canScrollList(int direction){
|
||||
const int childCount = getChildCount();
|
||||
if (childCount == 0) return false;
|
||||
|
||||
int firstPosition = mFirstPosition;
|
||||
Rect listPadding = mListPadding;
|
||||
if (direction > 0) {
|
||||
const int lastBottom = getChildAt(childCount - 1)->getBottom();
|
||||
const int lastPosition = firstPosition + childCount;
|
||||
return lastPosition < mItemCount || lastBottom > getHeight() - listPadding.height;//bottom;
|
||||
} else {
|
||||
const int firstTop = getChildAt(0)->getTop();
|
||||
return firstPosition > 0 || firstTop < listPadding.top;
|
||||
}
|
||||
}
|
||||
|
||||
bool AbsListView::trackMotionScroll(int deltaY, int incrementalDeltaY) {
|
||||
int childCount = getChildCount();
|
||||
if (childCount == 0) {
|
||||
|
@ -241,8 +241,8 @@ protected:
|
||||
void onMeasure(int widthMeasureSpec, int heightMeasureSpec)override;
|
||||
void updateScrollIndicators();
|
||||
void setScrollIndicatorViews(View* up, View* down);
|
||||
bool touchModeDrawsInPressedState();
|
||||
bool shouldShowSelector();
|
||||
virtual bool touchModeDrawsInPressedState();
|
||||
virtual bool shouldShowSelector();
|
||||
void updateSelectorState();
|
||||
void drawableStateChanged()override;
|
||||
virtual void layoutChildren();
|
||||
@ -253,7 +253,7 @@ protected:
|
||||
void confirmCheckedPositionsById();
|
||||
void handleDataChanged()override;
|
||||
static int getDistance(const Rect& source,const Rect& dest, int direction);
|
||||
View* obtainView(int position, bool*outMetadata);
|
||||
virtual View* obtainView(int position, bool*outMetadata);
|
||||
void positionSelector(int position, View* sel);
|
||||
void hideSelector();
|
||||
void reportScrollStateChange(int newState);
|
||||
@ -351,6 +351,8 @@ public:
|
||||
void smoothScrollBy(int distance, int duration);
|
||||
void smoothScrollBy(int distance, int duration, bool linear,bool suppressEndFlingStateChangeCall);
|
||||
void smoothScrollByOffset(int position);
|
||||
void scrollListBy(int y);
|
||||
bool canScrollList(int direction);
|
||||
void reclaimViews(std::vector<View*>& views);
|
||||
};
|
||||
|
||||
|
430
src/gui/widget/autoscrollhelper.cc
Executable file
430
src/gui/widget/autoscrollhelper.cc
Executable file
@ -0,0 +1,430 @@
|
||||
#include <widget/autoscrollhelper.h>
|
||||
#include <widget/abslistview.h>
|
||||
namespace cdroid{
|
||||
|
||||
AutoScrollHelper::AutoScrollHelper(View* target) {
|
||||
mTarget = target;
|
||||
|
||||
//DisplayMetrics metrics = Resources.getSystem().getDisplayMetrics();
|
||||
//int maxVelocity = (int) (DEFAULT_MAXIMUM_VELOCITY_DIPS * metrics.density + 0.5f);
|
||||
//int minVelocity = (int) (DEFAULT_MINIMUM_VELOCITY_DIPS * metrics.density + 0.5f);
|
||||
int maxVelocity = DEFAULT_MAXIMUM_VELOCITY_DIPS*160;
|
||||
int minVelocity = DEFAULT_MINIMUM_VELOCITY_DIPS*160;
|
||||
setMaximumVelocity(maxVelocity, maxVelocity);
|
||||
setMinimumVelocity(minVelocity, minVelocity);
|
||||
|
||||
setEdgeType(DEFAULT_EDGE_TYPE);
|
||||
setMaximumEdges(DEFAULT_MAXIMUM_EDGE, DEFAULT_MAXIMUM_EDGE);
|
||||
setRelativeEdges(DEFAULT_RELATIVE_EDGE, DEFAULT_RELATIVE_EDGE);
|
||||
setRelativeVelocity(DEFAULT_RELATIVE_VELOCITY, DEFAULT_RELATIVE_VELOCITY);
|
||||
setActivationDelay(DEFAULT_ACTIVATION_DELAY);
|
||||
setRampUpDuration(DEFAULT_RAMP_UP_DURATION);
|
||||
setRampDownDuration(DEFAULT_RAMP_DOWN_DURATION);
|
||||
|
||||
mScroller =new ClampedScroller();
|
||||
mEdgeInterpolator = new AccelerateInterpolator();
|
||||
}
|
||||
|
||||
AutoScrollHelper::~AutoScrollHelper(){
|
||||
delete mScroller;
|
||||
delete mEdgeInterpolator;
|
||||
}
|
||||
|
||||
AutoScrollHelper& AutoScrollHelper::setEnabled(bool enabled) {
|
||||
if (mEnabled && !enabled) {
|
||||
requestStop();
|
||||
}
|
||||
mEnabled = enabled;
|
||||
return *this;
|
||||
}
|
||||
|
||||
bool AutoScrollHelper::isEnabled()const{
|
||||
return mEnabled;
|
||||
}
|
||||
|
||||
AutoScrollHelper& AutoScrollHelper::setExclusive(bool exclusive) {
|
||||
mExclusive = exclusive;
|
||||
return *this;
|
||||
}
|
||||
|
||||
bool AutoScrollHelper::isExclusive()const{
|
||||
return mExclusive;
|
||||
}
|
||||
|
||||
AutoScrollHelper& AutoScrollHelper::setMaximumVelocity(float horizontalMax, float verticalMax) {
|
||||
mMaximumVelocity[HORIZONTAL] = horizontalMax / 1000.f;
|
||||
mMaximumVelocity[VERTICAL] = verticalMax / 1000.f;
|
||||
return *this;
|
||||
}
|
||||
|
||||
AutoScrollHelper& AutoScrollHelper::setMinimumVelocity(float horizontalMin, float verticalMin) {
|
||||
mMinimumVelocity[HORIZONTAL] = horizontalMin / 1000.f;
|
||||
mMinimumVelocity[VERTICAL] = verticalMin / 1000.f;
|
||||
return *this;
|
||||
}
|
||||
|
||||
AutoScrollHelper& AutoScrollHelper::setRelativeVelocity(float horizontal, float vertical){
|
||||
mRelativeVelocity[HORIZONTAL] = horizontal / 1000.f;
|
||||
mRelativeVelocity[VERTICAL] = vertical / 1000.f;
|
||||
return*this;
|
||||
}
|
||||
|
||||
AutoScrollHelper& AutoScrollHelper::setEdgeType(int type){
|
||||
mEdgeType =type;
|
||||
return *this;
|
||||
}
|
||||
|
||||
AutoScrollHelper& AutoScrollHelper::setRelativeEdges(float horizontal, float vertical) {
|
||||
mRelativeEdges[HORIZONTAL] = horizontal;
|
||||
mRelativeEdges[VERTICAL] = vertical;
|
||||
return *this;
|
||||
}
|
||||
|
||||
AutoScrollHelper& AutoScrollHelper::setMaximumEdges(float horizontalMax, float verticalMax) {
|
||||
mMaximumEdges[HORIZONTAL] = horizontalMax;
|
||||
mMaximumEdges[VERTICAL] = verticalMax;
|
||||
return *this;
|
||||
}
|
||||
|
||||
AutoScrollHelper& AutoScrollHelper::setActivationDelay(int delayMillis) {
|
||||
mActivationDelay = delayMillis;
|
||||
return *this;
|
||||
}
|
||||
|
||||
AutoScrollHelper& AutoScrollHelper::setRampUpDuration(int durationMillis) {
|
||||
mScroller->setRampUpDuration(durationMillis);
|
||||
return *this;
|
||||
}
|
||||
|
||||
AutoScrollHelper& AutoScrollHelper::setRampDownDuration(int durationMillis) {
|
||||
mScroller->setRampDownDuration(durationMillis);
|
||||
return *this;
|
||||
}
|
||||
|
||||
bool AutoScrollHelper::onTouch(View& v, MotionEvent& event) {
|
||||
if (!mEnabled) {
|
||||
return false;
|
||||
}
|
||||
float xTargetVelocity=.0,yTargetVelocity=.0;
|
||||
int action = event.getActionMasked();
|
||||
switch (action) {
|
||||
case MotionEvent::ACTION_DOWN:
|
||||
mNeedsCancel = true;
|
||||
mAlreadyDelayed = false;
|
||||
// $FALL-THROUGH$
|
||||
case MotionEvent::ACTION_MOVE:
|
||||
xTargetVelocity = computeTargetVelocity(
|
||||
HORIZONTAL, event.getX(), v.getWidth(), mTarget->getWidth());
|
||||
yTargetVelocity = computeTargetVelocity(
|
||||
VERTICAL, event.getY(), v.getHeight(), mTarget->getHeight());
|
||||
mScroller->setTargetVelocity(xTargetVelocity, yTargetVelocity);
|
||||
|
||||
// If the auto scroller was not previously active, but it should
|
||||
// be, then update the state and start animations.
|
||||
if (!mAnimating && shouldAnimate()) {
|
||||
startAnimating();
|
||||
}
|
||||
break;
|
||||
case MotionEvent::ACTION_UP:
|
||||
case MotionEvent::ACTION_CANCEL:
|
||||
requestStop();
|
||||
break;
|
||||
}
|
||||
return mExclusive && mAnimating;
|
||||
}
|
||||
|
||||
bool AutoScrollHelper::shouldAnimate() {
|
||||
const int verticalDirection = mScroller->getVerticalDirection();
|
||||
const int horizontalDirection = mScroller->getHorizontalDirection();
|
||||
|
||||
return verticalDirection != 0 && canTargetScrollVertically(verticalDirection)
|
||||
|| horizontalDirection != 0 && canTargetScrollHorizontally(horizontalDirection);
|
||||
}
|
||||
|
||||
|
||||
void AutoScrollHelper::startAnimating() {
|
||||
if (mRunnable == nullptr) {
|
||||
mRunnable = std::bind(&AutoScrollHelper::animationRun,this);
|
||||
}
|
||||
|
||||
mAnimating = true;
|
||||
mNeedsReset = true;
|
||||
|
||||
if (!mAlreadyDelayed && mActivationDelay > 0) {
|
||||
mTarget->postOnAnimationDelayed(mRunnable, mActivationDelay);
|
||||
} else {
|
||||
mRunnable();
|
||||
}
|
||||
|
||||
// If we start animating again before the user lifts their finger, we
|
||||
// already know it's not a tap and don't need an activation delay.
|
||||
mAlreadyDelayed = true;
|
||||
}
|
||||
|
||||
void AutoScrollHelper::requestStop() {
|
||||
if (mNeedsReset) {
|
||||
// The animation has been posted, but hasn't run yet. Manually
|
||||
// stopping animation will prevent it from running.
|
||||
mAnimating = false;
|
||||
} else {
|
||||
mScroller->requestStop();
|
||||
}
|
||||
}
|
||||
|
||||
template<typename T>
|
||||
T constrain(T value, T min, T max) {
|
||||
if (value > max) {
|
||||
return max;
|
||||
} else if (value < min) {
|
||||
return min;
|
||||
} else {
|
||||
return value;
|
||||
}
|
||||
}
|
||||
|
||||
float AutoScrollHelper::computeTargetVelocity(int direction, float coordinate, float srcSize, float dstSize) {
|
||||
float relativeEdge = mRelativeEdges[direction];
|
||||
float maximumEdge = mMaximumEdges[direction];
|
||||
float value = getEdgeValue(relativeEdge, srcSize, maximumEdge, coordinate);
|
||||
if (value == 0) {
|
||||
// The edge in this direction is not activated.
|
||||
return 0;
|
||||
}
|
||||
|
||||
float relativeVelocity = mRelativeVelocity[direction];
|
||||
float minimumVelocity = mMinimumVelocity[direction];
|
||||
float maximumVelocity = mMaximumVelocity[direction];
|
||||
float targetVelocity = relativeVelocity * dstSize;
|
||||
|
||||
// Target velocity is adjusted for interpolated edge position, then
|
||||
// clamped to the minimum and maximum values. Later, this value will be
|
||||
// adjusted for time-based acceleration.
|
||||
if (value > 0) {
|
||||
return constrain(value * targetVelocity, minimumVelocity, maximumVelocity);
|
||||
} else {
|
||||
return -constrain(-value * targetVelocity, minimumVelocity, maximumVelocity);
|
||||
}
|
||||
}
|
||||
|
||||
float AutoScrollHelper::getEdgeValue(float relativeValue, float size, float maxValue, float current) {
|
||||
// For now, leading and trailing edges are always the same size.
|
||||
const float edgeSize = constrain(relativeValue * size, NO_MIN, maxValue);
|
||||
const float valueLeading = constrainEdgeValue(current, edgeSize);
|
||||
const float valueTrailing = constrainEdgeValue(size - current, edgeSize);
|
||||
const float value = (valueTrailing - valueLeading);
|
||||
float interpolated;
|
||||
if (value < 0) {
|
||||
interpolated = -mEdgeInterpolator->getInterpolation(-value);
|
||||
} else if (value > 0) {
|
||||
interpolated = mEdgeInterpolator->getInterpolation(value);
|
||||
} else {
|
||||
return 0;
|
||||
}
|
||||
|
||||
return constrain(interpolated, -1.f, 1.f);
|
||||
}
|
||||
|
||||
float AutoScrollHelper::constrainEdgeValue(float current, float leading) {
|
||||
if (leading == 0) return 0;
|
||||
|
||||
switch (mEdgeType) {
|
||||
case EDGE_TYPE_INSIDE:
|
||||
case EDGE_TYPE_INSIDE_EXTEND:
|
||||
if (current < leading) {
|
||||
if (current >= 0) {
|
||||
// Movement up to the edge is scaled.
|
||||
return 1.f - current / leading;
|
||||
} else if (mAnimating && (mEdgeType == EDGE_TYPE_INSIDE_EXTEND)) {
|
||||
// Movement beyond the edge is always maximum.
|
||||
return 1.f;
|
||||
}
|
||||
}
|
||||
break;
|
||||
case EDGE_TYPE_OUTSIDE:
|
||||
if (current < 0) {
|
||||
// Movement beyond the edge is scaled.
|
||||
return current / -leading;
|
||||
}
|
||||
break;
|
||||
}
|
||||
return 0;
|
||||
}
|
||||
|
||||
void AutoScrollHelper::cancelTargetTouch() {
|
||||
long eventTime = SystemClock::uptimeMillis();
|
||||
MotionEvent* cancel = MotionEvent::obtain(
|
||||
eventTime, eventTime, MotionEvent::ACTION_CANCEL, 0, 0, 0);
|
||||
mTarget->onTouchEvent(*cancel);
|
||||
cancel->recycle();
|
||||
}
|
||||
|
||||
void AutoScrollHelper::animationRun(){
|
||||
if (!mAnimating) {
|
||||
return;
|
||||
}
|
||||
if (mNeedsReset) {
|
||||
mNeedsReset = false;
|
||||
mScroller->start();
|
||||
}
|
||||
|
||||
ClampedScroller* scroller = mScroller;
|
||||
if (scroller->isFinished() || !shouldAnimate()) {
|
||||
mAnimating = false;
|
||||
return;
|
||||
}
|
||||
|
||||
if (mNeedsCancel) {
|
||||
mNeedsCancel = false;
|
||||
cancelTargetTouch();
|
||||
}
|
||||
|
||||
scroller->computeScrollDelta();
|
||||
|
||||
const int deltaX = scroller->getDeltaX();
|
||||
const int deltaY = scroller->getDeltaY();
|
||||
scrollTargetBy(deltaX, deltaY);
|
||||
|
||||
// Keep going until the scroller has permanently stopped.
|
||||
mTarget->postOnAnimation(mRunnable);
|
||||
}
|
||||
/////////////////////////////////////////////////////////////////////////////////////
|
||||
AutoScrollHelper::ClampedScroller::ClampedScroller() {
|
||||
mStartTime = LONG_MIN;//Long.MIN_VALUE;
|
||||
mStopTime = -1;
|
||||
mDeltaTime = 0;
|
||||
mDeltaX = 0;
|
||||
mDeltaY = 0;
|
||||
}
|
||||
|
||||
void AutoScrollHelper::ClampedScroller::setRampUpDuration(int durationMillis) {
|
||||
mRampUpDuration = durationMillis;
|
||||
}
|
||||
|
||||
void AutoScrollHelper::ClampedScroller::setRampDownDuration(int durationMillis) {
|
||||
mRampDownDuration = durationMillis;
|
||||
}
|
||||
|
||||
void AutoScrollHelper::ClampedScroller::start() {
|
||||
mStartTime = AnimationUtils::currentAnimationTimeMillis();
|
||||
mStopTime = -1;
|
||||
mDeltaTime = mStartTime;
|
||||
mStopValue = 0.5f;
|
||||
mDeltaX = 0;
|
||||
mDeltaY = 0;
|
||||
}
|
||||
|
||||
|
||||
void AutoScrollHelper::ClampedScroller::requestStop() {
|
||||
long currentTime = AnimationUtils::currentAnimationTimeMillis();
|
||||
mEffectiveRampDown = constrain((int) (currentTime - mStartTime), 0, mRampDownDuration);
|
||||
mStopValue = getValueAt(currentTime);
|
||||
mStopTime = currentTime;
|
||||
}
|
||||
|
||||
bool AutoScrollHelper::ClampedScroller::isFinished() {
|
||||
return mStopTime > 0
|
||||
&& AnimationUtils::currentAnimationTimeMillis() > mStopTime + mEffectiveRampDown;
|
||||
}
|
||||
|
||||
float AutoScrollHelper::ClampedScroller::getValueAt(long currentTime) {
|
||||
if (currentTime < mStartTime) {
|
||||
return .0f;
|
||||
} else if (mStopTime < 0 || currentTime < mStopTime) {
|
||||
long elapsedSinceStart = currentTime - mStartTime;
|
||||
return 0.5f * constrain(elapsedSinceStart / (float) mRampUpDuration, .0f, 1.f);
|
||||
} else {
|
||||
long elapsedSinceEnd = currentTime - mStopTime;
|
||||
return (1.f - mStopValue) + mStopValue
|
||||
* constrain(elapsedSinceEnd / (float) mEffectiveRampDown, .0f, 1.f);
|
||||
}
|
||||
}
|
||||
|
||||
float AutoScrollHelper::ClampedScroller::interpolateValue(float value) {
|
||||
return -4 * value * value + 4 * value;
|
||||
}
|
||||
|
||||
void AutoScrollHelper::ClampedScroller::computeScrollDelta() {
|
||||
if (mDeltaTime == 0) {
|
||||
throw "Cannot compute scroll delta before calling start()";
|
||||
}
|
||||
|
||||
long currentTime = AnimationUtils::currentAnimationTimeMillis();
|
||||
float value = getValueAt(currentTime);
|
||||
float scale = interpolateValue(value);
|
||||
long elapsedSinceDelta = currentTime - mDeltaTime;
|
||||
|
||||
mDeltaTime = currentTime;
|
||||
mDeltaX = (int) (elapsedSinceDelta * scale * mTargetVelocityX);
|
||||
mDeltaY = (int) (elapsedSinceDelta * scale * mTargetVelocityY);
|
||||
}
|
||||
|
||||
void AutoScrollHelper::ClampedScroller::setTargetVelocity(float x, float y) {
|
||||
mTargetVelocityX = x;
|
||||
mTargetVelocityY = y;
|
||||
}
|
||||
|
||||
int AutoScrollHelper::ClampedScroller::getHorizontalDirection()const{
|
||||
return (int) (mTargetVelocityX / std::abs(mTargetVelocityX));
|
||||
}
|
||||
|
||||
int AutoScrollHelper::ClampedScroller::getVerticalDirection()const{
|
||||
return (int) (mTargetVelocityY / std::abs(mTargetVelocityY));
|
||||
}
|
||||
|
||||
int AutoScrollHelper::ClampedScroller::getDeltaX()const {
|
||||
return mDeltaX;
|
||||
}
|
||||
|
||||
int AutoScrollHelper::ClampedScroller::getDeltaY()const {
|
||||
return mDeltaY;
|
||||
}
|
||||
|
||||
////////////////////////////////////////////////////////////////////////////////////////////
|
||||
AbsListViewAutoScroller::AbsListViewAutoScroller(AbsListView* target):AutoScrollHelper(target){
|
||||
mTarget = target;
|
||||
}
|
||||
|
||||
void AbsListViewAutoScroller::scrollTargetBy(int deltaX, int deltaY) {
|
||||
((AbsListView*)mTarget)->scrollListBy(deltaY);
|
||||
}
|
||||
|
||||
bool AbsListViewAutoScroller::canTargetScrollHorizontally(int direction) {
|
||||
// List do not scroll horizontally.
|
||||
return false;
|
||||
}
|
||||
|
||||
bool AbsListViewAutoScroller::canTargetScrollVertically(int direction) {
|
||||
AbsListView* target = (AbsListView*)mTarget;
|
||||
int itemCount = target->getCount();
|
||||
if (itemCount == 0) {
|
||||
return false;
|
||||
}
|
||||
|
||||
int childCount = target->getChildCount();
|
||||
int firstPosition = target->getFirstVisiblePosition();
|
||||
int lastPosition = firstPosition + childCount;
|
||||
|
||||
if (direction > 0) {
|
||||
// Are we already showing the entire last item?
|
||||
if (lastPosition >= itemCount) {
|
||||
View* lastView = target->getChildAt(childCount - 1);
|
||||
if (lastView->getBottom() <= target->getHeight()) {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
} else if (direction < 0) {
|
||||
// Are we already showing the entire first item?
|
||||
if (firstPosition <= 0) {
|
||||
View* firstView = target->getChildAt(0);
|
||||
if (firstView->getTop() >= 0) {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
} else {
|
||||
// The behavior for direction 0 is undefined and we can return
|
||||
// whatever we want.
|
||||
return false;
|
||||
}
|
||||
return true;
|
||||
}
|
||||
}
|
126
src/gui/widget/autoscrollhelper.h
Executable file
126
src/gui/widget/autoscrollhelper.h
Executable file
@ -0,0 +1,126 @@
|
||||
#pragma once
|
||||
#include <widget/view.h>
|
||||
#include <limits.h>
|
||||
namespace cdroid{
|
||||
class AbsListView;
|
||||
class AutoScrollHelper{
|
||||
public:
|
||||
static constexpr float RELATIVE_UNSPECIFIED = 0;
|
||||
static constexpr float NO_MAX = 100000.f;//FLT_MAX;//Float.MAX_VALUE;
|
||||
static constexpr float NO_MIN = 0;
|
||||
static constexpr int EDGE_TYPE_INSIDE = 0;
|
||||
static constexpr int EDGE_TYPE_INSIDE_EXTEND = 1;
|
||||
static constexpr int EDGE_TYPE_OUTSIDE = 2;
|
||||
private:
|
||||
static constexpr int HORIZONTAL = 0;
|
||||
static constexpr int VERTICAL = 1;
|
||||
static constexpr int DEFAULT_EDGE_TYPE = EDGE_TYPE_INSIDE_EXTEND;
|
||||
static constexpr int DEFAULT_MINIMUM_VELOCITY_DIPS = 315;
|
||||
static constexpr int DEFAULT_MAXIMUM_VELOCITY_DIPS = 1575;
|
||||
static constexpr float DEFAULT_MAXIMUM_EDGE = NO_MAX;
|
||||
static constexpr float DEFAULT_RELATIVE_EDGE = 0.2f;
|
||||
static constexpr float DEFAULT_RELATIVE_VELOCITY = 1.f;
|
||||
static constexpr int DEFAULT_ACTIVATION_DELAY = 10;//ViewConfiguration::getTapTimeout();
|
||||
static constexpr int DEFAULT_RAMP_UP_DURATION = 500;
|
||||
static constexpr int DEFAULT_RAMP_DOWN_DURATION = 500;
|
||||
|
||||
class ClampedScroller{
|
||||
private:
|
||||
int mRampUpDuration;
|
||||
int mRampDownDuration;
|
||||
float mTargetVelocityX;
|
||||
float mTargetVelocityY;
|
||||
|
||||
long mStartTime;
|
||||
|
||||
long mDeltaTime;
|
||||
int mDeltaX;
|
||||
int mDeltaY;
|
||||
|
||||
long mStopTime;
|
||||
float mStopValue;
|
||||
int mEffectiveRampDown;
|
||||
float getValueAt(long currentTime);
|
||||
float interpolateValue(float value);
|
||||
public:
|
||||
ClampedScroller();
|
||||
void setRampUpDuration(int durationMillis);
|
||||
void setRampDownDuration(int durationMillis);
|
||||
void start();
|
||||
void requestStop();
|
||||
bool isFinished();
|
||||
void computeScrollDelta();
|
||||
void setTargetVelocity(float x, float y);
|
||||
int getHorizontalDirection()const;
|
||||
int getVerticalDirection()const;
|
||||
int getDeltaX()const;
|
||||
int getDeltaY()const;
|
||||
};
|
||||
ClampedScroller* mScroller;
|
||||
Interpolator* mEdgeInterpolator;
|
||||
protected: View* mTarget;
|
||||
Runnable mRunnable;
|
||||
float mRelativeEdges[2];
|
||||
float mMaximumEdges[2];
|
||||
int mEdgeType;
|
||||
int mActivationDelay;
|
||||
float mRelativeVelocity[2];
|
||||
float mMinimumVelocity[2];
|
||||
float mMaximumVelocity[2];
|
||||
bool mAlreadyDelayed;
|
||||
|
||||
/** Whether to reset the scroller start time on the next animation. */
|
||||
bool mNeedsReset;
|
||||
|
||||
/** Whether to send a cancel motion event to the target view. */
|
||||
bool mNeedsCancel;
|
||||
|
||||
/** Whether the auto-scroller is actively scrolling. */
|
||||
bool mAnimating;
|
||||
|
||||
/** Whether the auto-scroller is enabled. */
|
||||
bool mEnabled;
|
||||
|
||||
/** Whether the auto-scroller consumes events when scrolling. */
|
||||
bool mExclusive;
|
||||
private:
|
||||
bool shouldAnimate();
|
||||
void startAnimating();
|
||||
void requestStop();
|
||||
float computeTargetVelocity(int direction, float coordinate, float srcSize, float dstSize);
|
||||
float getEdgeValue(float relativeValue, float size, float maxValue, float current);
|
||||
float constrainEdgeValue(float current, float leading);
|
||||
void cancelTargetTouch();
|
||||
void animationRun();
|
||||
public:
|
||||
AutoScrollHelper(View*target);
|
||||
virtual ~AutoScrollHelper();
|
||||
AutoScrollHelper& setEnabled(bool enabled);
|
||||
bool isEnabled()const;
|
||||
AutoScrollHelper& setExclusive(bool exclusive);
|
||||
bool isExclusive()const;
|
||||
AutoScrollHelper& setMaximumVelocity(float horizontalMax, float verticalMax);
|
||||
AutoScrollHelper& setMinimumVelocity(float horizontalMin, float verticalMin);
|
||||
AutoScrollHelper& setRelativeVelocity(float horizontal, float vertical);
|
||||
AutoScrollHelper& setEdgeType(int type);
|
||||
AutoScrollHelper& setRelativeEdges(float horizontal, float vertical);
|
||||
AutoScrollHelper& setMaximumEdges(float horizontalMax, float verticalMax);
|
||||
AutoScrollHelper& setActivationDelay(int delayMillis);
|
||||
AutoScrollHelper& setRampUpDuration(int durationMillis);
|
||||
AutoScrollHelper& setRampDownDuration(int durationMillis);
|
||||
bool onTouch(View& v, MotionEvent& event);
|
||||
|
||||
virtual void scrollTargetBy(int deltaX, int deltaY)=0;
|
||||
virtual bool canTargetScrollHorizontally(int direction)=0;
|
||||
virtual bool canTargetScrollVertically(int direction)=0;
|
||||
};
|
||||
|
||||
class AbsListViewAutoScroller:public AutoScrollHelper{
|
||||
public:
|
||||
AbsListViewAutoScroller(AbsListView* target);
|
||||
void scrollTargetBy(int deltaX, int deltaY)override;
|
||||
bool canTargetScrollHorizontally(int direction)override;
|
||||
bool canTargetScrollVertically(int direction)override;
|
||||
};
|
||||
|
||||
}
|
207
src/gui/widget/dropdownlistview.cc
Executable file
207
src/gui/widget/dropdownlistview.cc
Executable file
@ -0,0 +1,207 @@
|
||||
#include <widget/dropdownlistview.h>
|
||||
#include <widget/textview.h>
|
||||
|
||||
namespace cdroid{
|
||||
|
||||
DropDownListView::DropDownListView(Context*context,bool hijackfoxus):ListView(context,AttributeSet()){
|
||||
|
||||
}
|
||||
|
||||
bool DropDownListView::shouldShowSelector(){
|
||||
return isHovered() || ListView::shouldShowSelector();
|
||||
}
|
||||
|
||||
bool DropDownListView::onTouchEvent(MotionEvent& ev){
|
||||
if(mResolveHoverRunnable){
|
||||
removeCallbacks(mResolveHoverRunnable);
|
||||
mResolveHoverRunnable=nullptr;
|
||||
}
|
||||
return ListView::onTouchEvent(ev);
|
||||
}
|
||||
|
||||
bool DropDownListView::onHoverEvent(MotionEvent& ev){
|
||||
int action = ev.getActionMasked();
|
||||
if (action == MotionEvent::ACTION_HOVER_EXIT && mResolveHoverRunnable == nullptr) {
|
||||
// This may be transitioning to TOUCH_DOWN. Postpone drawable state
|
||||
// updates until either the next frame or the next touch event.
|
||||
mResolveHoverRunnable = [this](){
|
||||
mResolveHoverRunnable=nullptr;
|
||||
drawableStateChanged();
|
||||
};
|
||||
post(mResolveHoverRunnable);
|
||||
}
|
||||
|
||||
// Allow the super class to handle hover state management first.
|
||||
bool handled = ListView::onHoverEvent(ev);
|
||||
|
||||
if (action == MotionEvent::ACTION_HOVER_ENTER || action == MotionEvent::ACTION_HOVER_MOVE) {
|
||||
int position = pointToPosition((int) ev.getX(), (int) ev.getY());
|
||||
if (position != INVALID_POSITION && position != mSelectedPosition) {
|
||||
View* hoveredItem = getChildAt(position - getFirstVisiblePosition());
|
||||
if (hoveredItem->isEnabled()) {
|
||||
// Force a focus so that the proper selector state gets
|
||||
// used when we update.
|
||||
requestFocus();
|
||||
|
||||
positionSelector(position, hoveredItem);
|
||||
setSelectedPositionInt(position);
|
||||
setNextSelectedPositionInt(position);
|
||||
}
|
||||
updateSelectorState();
|
||||
}
|
||||
} else {
|
||||
// Do not cancel the selected position if the selection is visible
|
||||
// by other means.
|
||||
if (!ListView::shouldShowSelector()) {
|
||||
setSelectedPositionInt(INVALID_POSITION);
|
||||
setNextSelectedPositionInt(INVALID_POSITION);
|
||||
}
|
||||
}
|
||||
|
||||
return handled;
|
||||
}
|
||||
|
||||
void DropDownListView::drawableStateChanged() {
|
||||
if (mResolveHoverRunnable == nullptr) {
|
||||
ListView::drawableStateChanged();
|
||||
}
|
||||
}
|
||||
|
||||
bool DropDownListView::onForwardedEvent(MotionEvent& event, int activePointerId) {
|
||||
bool handledEvent = true;
|
||||
bool bClearPressedItem = false;
|
||||
int x,y,activeIndex,position;
|
||||
const int actionMasked = event.getActionMasked();
|
||||
switch (actionMasked) {
|
||||
case MotionEvent::ACTION_CANCEL:
|
||||
handledEvent = false;
|
||||
break;
|
||||
case MotionEvent::ACTION_UP:
|
||||
handledEvent = false;
|
||||
// $FALL-THROUGH$
|
||||
case MotionEvent::ACTION_MOVE:
|
||||
activeIndex = event.findPointerIndex(activePointerId);
|
||||
if (activeIndex < 0) {
|
||||
handledEvent = false;
|
||||
break;
|
||||
}
|
||||
|
||||
x = (int) event.getX(activeIndex);
|
||||
y = (int) event.getY(activeIndex);
|
||||
position = pointToPosition(x, y);
|
||||
if (position == INVALID_POSITION) {
|
||||
bClearPressedItem = true;
|
||||
break;
|
||||
}
|
||||
|
||||
View* child = getChildAt(position - getFirstVisiblePosition());
|
||||
setPressedItem(child, position, x, y);
|
||||
handledEvent = true;
|
||||
|
||||
if (actionMasked == MotionEvent::ACTION_UP) {
|
||||
long id = getItemIdAtPosition(position);
|
||||
performItemClick(child, position, id);
|
||||
}
|
||||
break;
|
||||
}
|
||||
|
||||
// Failure to handle the event cancels forwarding.
|
||||
if (!handledEvent || bClearPressedItem) {
|
||||
clearPressedItem();
|
||||
}
|
||||
|
||||
// Manage automatic scrolling.
|
||||
if (handledEvent) {
|
||||
if (mScrollHelper == nullptr) {
|
||||
mScrollHelper = new AbsListViewAutoScroller(this);
|
||||
}
|
||||
mScrollHelper->setEnabled(true);
|
||||
mScrollHelper->onTouch(*this, event);
|
||||
} else if (mScrollHelper != nullptr) {
|
||||
mScrollHelper->setEnabled(false);
|
||||
}
|
||||
|
||||
return handledEvent;
|
||||
}
|
||||
|
||||
void DropDownListView::setListSelectionHidden(bool hideListSelection) {
|
||||
mListSelectionHidden = hideListSelection;
|
||||
}
|
||||
|
||||
void DropDownListView::clearPressedItem() {
|
||||
mDrawsInPressedState = false;
|
||||
setPressed(false);
|
||||
updateSelectorState();
|
||||
|
||||
View* motionView = getChildAt(mMotionPosition - mFirstPosition);
|
||||
if (motionView) motionView->setPressed(false);
|
||||
}
|
||||
|
||||
void DropDownListView::setPressedItem(View* child, int position, float x, float y) {
|
||||
mDrawsInPressedState = true;
|
||||
|
||||
// Ordering is essential. First, update the container's pressed state.
|
||||
drawableHotspotChanged(x, y);
|
||||
if (!isPressed()) {
|
||||
setPressed(true);
|
||||
}
|
||||
|
||||
// Next, run layout if we need to stabilize child positions.
|
||||
if (mDataChanged) {
|
||||
layoutChildren();
|
||||
}
|
||||
|
||||
// Manage the pressed view based on motion position. This allows us to
|
||||
// play nicely with actual touch and scroll events.
|
||||
View* motionView = getChildAt(mMotionPosition - mFirstPosition);
|
||||
if (motionView && motionView != child && motionView->isPressed()) {
|
||||
motionView->setPressed(false);
|
||||
}
|
||||
mMotionPosition = position;
|
||||
|
||||
// Offset for child coordinates.
|
||||
float childX = x - child->getLeft();
|
||||
float childY = y - child->getTop();
|
||||
child->drawableHotspotChanged(childX, childY);
|
||||
if (!child->isPressed()) {
|
||||
child->setPressed(true);
|
||||
}
|
||||
|
||||
// Ensure that keyboard focus starts from the last touched position.
|
||||
setSelectedPositionInt(position);
|
||||
positionSelectorLikeTouch(position, child, x, y);
|
||||
|
||||
// Refresh the drawable state to reflect the new pressed state,
|
||||
// which will also update the selector state.
|
||||
refreshDrawableState();
|
||||
}
|
||||
|
||||
bool DropDownListView::touchModeDrawsInPressedState() {
|
||||
return mDrawsInPressedState || ListView::touchModeDrawsInPressedState();
|
||||
}
|
||||
|
||||
View* DropDownListView::obtainView(int position, bool* isScrap) {
|
||||
View* view = ListView::obtainView(position, isScrap);
|
||||
if (dynamic_cast<TextView*>(view)) {
|
||||
//((TextView*) view)->setHorizontallyScrolling(true);
|
||||
}
|
||||
return view;
|
||||
}
|
||||
|
||||
bool DropDownListView::isInTouchMode()const{
|
||||
// WARNING: Please read the comment where mListSelectionHidden is declared
|
||||
return (mHijackFocus && mListSelectionHidden) || ListView::isInTouchMode();
|
||||
}
|
||||
|
||||
bool DropDownListView::hasWindowFocus()const{
|
||||
return mHijackFocus || ListView::hasWindowFocus();
|
||||
}
|
||||
|
||||
bool DropDownListView::isFocused()const{
|
||||
return mHijackFocus || ListView::isFocused();
|
||||
}
|
||||
|
||||
bool DropDownListView::hasFocus()const{
|
||||
return mHijackFocus || ListView::hasFocus();
|
||||
}
|
||||
}
|
36
src/gui/widget/dropdownlistview.h
Executable file
36
src/gui/widget/dropdownlistview.h
Executable file
@ -0,0 +1,36 @@
|
||||
#pragma once
|
||||
#include <widget/listview.h>
|
||||
#include <widget/autoscrollhelper.h>
|
||||
namespace cdroid{
|
||||
|
||||
class DropDownListView:public ListView{
|
||||
private:
|
||||
bool mListSelectionHidden;
|
||||
bool mHijackFocus;
|
||||
bool mDrawsInPressedState;
|
||||
AbsListViewAutoScroller* mScrollHelper;
|
||||
Runnable mResolveHoverRunnable;
|
||||
void clearPressedItem();
|
||||
void setPressedItem(View* child, int position, float x, float y);
|
||||
protected:
|
||||
void drawableStateChanged()override;
|
||||
bool touchModeDrawsInPressedState()override;
|
||||
View* obtainView(int position, bool* isScrap)override;
|
||||
public:
|
||||
DropDownListView(Context*,bool hijackfoxus);
|
||||
bool shouldShowSelector()override;
|
||||
bool onTouchEvent(MotionEvent& ev)override;
|
||||
bool onHoverEvent(MotionEvent& ev)override;
|
||||
bool onForwardedEvent(MotionEvent& event, int activePointerId);
|
||||
void setListSelectionHidden(bool hideListSelection);
|
||||
bool isInTouchMode()const override;
|
||||
bool hasWindowFocus()const override;
|
||||
bool isFocused()const override;
|
||||
bool hasFocus()const override;
|
||||
virtual void scrollTargetBy(int deltaX, int deltaY)=0;
|
||||
virtual bool canTargetScrollHorizontally(int direction)=0;
|
||||
virtual bool canTargetScrollVertically(int direction)=0;
|
||||
};
|
||||
|
||||
}
|
||||
|
@ -17,7 +17,7 @@ LayoutInflater*LayoutInflater::from(Context*context){
|
||||
return new LayoutInflater(context);
|
||||
}
|
||||
|
||||
LayoutInflater::ViewInflater LayoutInflater::getViewInflater(const std::string&name){
|
||||
LayoutInflater::ViewInflater LayoutInflater::getInflater(const std::string&name){
|
||||
std::map<const std::string,ViewInflater>&maps=LayoutInflater::getMap();
|
||||
auto it=maps.find(name);
|
||||
return (it!=maps.end())?it->second:nullptr;
|
||||
@ -64,7 +64,7 @@ typedef struct{
|
||||
static void startElement(void *userData, const XML_Char *name, const XML_Char **satts){
|
||||
WindowParserData*pd=(WindowParserData*)userData;
|
||||
AttributeSet atts(satts);
|
||||
LayoutInflater::ViewInflater inflater=LayoutInflater::getViewInflater(name);
|
||||
LayoutInflater::ViewInflater inflater=LayoutInflater::getInflater(name);
|
||||
ViewGroup*parent=nullptr;
|
||||
if(pd->views.size())
|
||||
parent=dynamic_cast<ViewGroup*>(pd->views.back());
|
||||
|
@ -15,22 +15,22 @@ private:
|
||||
static std::map<const std::string,ViewInflater>&getMap();
|
||||
public:
|
||||
static LayoutInflater*from(Context*context);
|
||||
static ViewInflater getViewInflater(const std::string&);
|
||||
static bool registInflater(const std::string&name,ViewInflater fun);
|
||||
static ViewInflater getInflater(const std::string&);
|
||||
View* inflate(std::istream&stream,ViewGroup*root);
|
||||
View* inflate(const std::string&resource,ViewGroup* root);
|
||||
View* inflate(const std::string&resource,ViewGroup* root, bool attachToRoot);
|
||||
};
|
||||
|
||||
template<typename T>
|
||||
class InflateRegister{
|
||||
class InflaterRegister{
|
||||
public:
|
||||
InflateRegister(const std::string&name){
|
||||
InflaterRegister(const std::string&name){
|
||||
LayoutInflater::registInflater(name,[](Context*ctx,const AttributeSet&attr)->View*{return new T(ctx,attr);});
|
||||
}
|
||||
};
|
||||
|
||||
#define DECLARE_WIDGET(T) static InflateRegister<T> widget_inflater_##T(#T);
|
||||
#define DECLARE_WIDGET(T) static InflaterRegister<T> widget_inflater_##T(#T);
|
||||
|
||||
}//endof namespace
|
||||
#endif
|
||||
|
@ -433,7 +433,7 @@ protected:
|
||||
void invalidateParentIfNeededAndWasQuickRejected();
|
||||
void destroyDrawingCache();
|
||||
RefPtr<ImageSurface>getDrawingCache(bool autoScale);
|
||||
bool hasWindowFocus()const;
|
||||
virtual bool hasWindowFocus()const;
|
||||
|
||||
virtual bool setFrame(int x,int y,int w,int h);
|
||||
virtual void resetResolvedDrawables();
|
||||
@ -462,8 +462,6 @@ protected:
|
||||
bool awakenScrollBars();
|
||||
bool awakenScrollBars(int startDelay, bool invalidate);
|
||||
|
||||
void postOnAnimation(Runnable& action);
|
||||
void postOnAnimationDelayed(Runnable& action, uint32_t delayMillis);
|
||||
static int combineVisibility(int vis1, int vis2);
|
||||
virtual void onSizeChanged(int w,int h,int oldw,int oldh);
|
||||
virtual void onScrollChanged(int l, int t, int oldl, int oldt);
|
||||
@ -777,7 +775,7 @@ public:
|
||||
void setFocusable(bool);
|
||||
int getFocusable()const;
|
||||
virtual void unFocus(View*);
|
||||
bool hasFocus()const;
|
||||
virtual bool hasFocus()const;
|
||||
virtual bool restoreFocusInCluster(int direction);
|
||||
virtual bool restoreFocusNotInCluster();
|
||||
virtual bool restoreDefaultFocus();
|
||||
@ -830,6 +828,8 @@ public:
|
||||
virtual bool onGenericMotionEvent(MotionEvent& event);
|
||||
virtual void onHoverChanged(bool hovered);
|
||||
|
||||
void postOnAnimation(Runnable& action);
|
||||
void postOnAnimationDelayed(Runnable& action, uint32_t delayMillis);
|
||||
bool post(Runnable& what);
|
||||
bool post(const std::function<void()>&what);
|
||||
bool postDelayed(const std::function<void()>&what,uint32_t delay=0);
|
||||
|
Loading…
Reference in New Issue
Block a user